Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
| Comment: | Add webserver |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA1: |
7c384fbb38226b0bcc8d7ec513f0d573 |
| User & Date: | nalck 2016-01-19 06:29:51.572 |
|
2016-01-19
| ||
| 23:26 | Restyle layout check-in: 898a546278 user: nalck tags: trunk | |
| 06:29 | Add webserver check-in: 7c384fbb38 user: nalck tags: trunk | |
|
2016-01-18
| ||
| 21:28 | Single-page redesign check-in: 4788cc20e7 user: nalck tags: trunk | |
|
| | | 1 2 3 4 5 6 7 8 | Copyright (c) 2016, Nathaniel Alcock Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| ︙ | ︙ |
> > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# Antigone
Antigone is a simple static site server. All files located in
the `www` directory are matched and served.
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
1. Add antigone to your list of dependencies in `mix.exs`:
def deps do
[{:antigone, "~> 0.0.1"}]
end
2. Ensure antigone is started before your application:
def application do
[applications: [:antigone]]
end
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > | 1 2 3 4 5 6 7 |
{application,antigone,
[{registered,[]},
{description,"antigone"},
{applications,[kernel,stdlib,elixir,logger,cowboy,plug]},
{mod,{'Elixir.Antigone',[]}},
{vsn,"0.0.1"},
{modules,['Elixir.Antigone','Elixir.Router','Elixir.Static']}]}.
|
> > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{application,cowboy,
[{description,"Small, fast, modular HTTP server."},
{vsn,"1.0.4"},
{id,"git"},
{modules,[cowboy,cowboy_app,cowboy_bstr,cowboy_clock,
cowboy_handler,cowboy_http,cowboy_http_handler,
cowboy_loop_handler,cowboy_middleware,cowboy_protocol,
cowboy_req,cowboy_rest,cowboy_router,cowboy_spdy,
cowboy_static,cowboy_sub_protocol,cowboy_sup,
cowboy_websocket,cowboy_websocket_handler]},
{registered,[cowboy_clock,cowboy_sup]},
{applications,[kernel,stdlib,ranch,cowlib,crypto]},
{mod,{cowboy_app,[]}},
{env,[]}]}.
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > | 1 2 3 4 5 6 7 8 |
{application,cowlib,
[{description,"Support library for manipulating Web protocols."},
{vsn,"1.0.2"},
{id,"git"},
{modules,[cow_cookie,cow_date,cow_http,cow_http_hd,cow_http_te,
cow_mimetypes,cow_multipart,cow_qs,cow_spdy]},
{registered,[]},
{applications,[kernel,stdlib,crypto]}]}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | %% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu> %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -ifndef(COW_INLINE_HRL). -define(COW_INLINE_HRL, 1). %% INLINE_LOWERCASE(Function, Rest, Acc, ...) %% %% To be included at the end of a case block. %% Defined for up to 10 extra arguments. -define(INLINE_LOWERCASE(Function, Rest, Acc), $A -> Function(Rest, << Acc/binary, $a >>); $B -> Function(Rest, << Acc/binary, $b >>); $C -> Function(Rest, << Acc/binary, $c >>); $D -> Function(Rest, << Acc/binary, $d >>); $E -> Function(Rest, << Acc/binary, $e >>); $F -> Function(Rest, << Acc/binary, $f >>); $G -> Function(Rest, << Acc/binary, $g >>); $H -> Function(Rest, << Acc/binary, $h >>); $I -> Function(Rest, << Acc/binary, $i >>); $J -> Function(Rest, << Acc/binary, $j >>); $K -> Function(Rest, << Acc/binary, $k >>); $L -> Function(Rest, << Acc/binary, $l >>); $M -> Function(Rest, << Acc/binary, $m >>); $N -> Function(Rest, << Acc/binary, $n >>); $O -> Function(Rest, << Acc/binary, $o >>); $P -> Function(Rest, << Acc/binary, $p >>); $Q -> Function(Rest, << Acc/binary, $q >>); $R -> Function(Rest, << Acc/binary, $r >>); $S -> Function(Rest, << Acc/binary, $s >>); $T -> Function(Rest, << Acc/binary, $t >>); $U -> Function(Rest, << Acc/binary, $u >>); $V -> Function(Rest, << Acc/binary, $v >>); $W -> Function(Rest, << Acc/binary, $w >>); $X -> Function(Rest, << Acc/binary, $x >>); $Y -> Function(Rest, << Acc/binary, $y >>); $Z -> Function(Rest, << Acc/binary, $z >>); C -> Function(Rest, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, Acc), $A -> Function(Rest, A0, << Acc/binary, $a >>); $B -> Function(Rest, A0, << Acc/binary, $b >>); $C -> Function(Rest, A0, << Acc/binary, $c >>); $D -> Function(Rest, A0, << Acc/binary, $d >>); $E -> Function(Rest, A0, << Acc/binary, $e >>); $F -> Function(Rest, A0, << Acc/binary, $f >>); $G -> Function(Rest, A0, << Acc/binary, $g >>); $H -> Function(Rest, A0, << Acc/binary, $h >>); $I -> Function(Rest, A0, << Acc/binary, $i >>); $J -> Function(Rest, A0, << Acc/binary, $j >>); $K -> Function(Rest, A0, << Acc/binary, $k >>); $L -> Function(Rest, A0, << Acc/binary, $l >>); $M -> Function(Rest, A0, << Acc/binary, $m >>); $N -> Function(Rest, A0, << Acc/binary, $n >>); $O -> Function(Rest, A0, << Acc/binary, $o >>); $P -> Function(Rest, A0, << Acc/binary, $p >>); $Q -> Function(Rest, A0, << Acc/binary, $q >>); $R -> Function(Rest, A0, << Acc/binary, $r >>); $S -> Function(Rest, A0, << Acc/binary, $s >>); $T -> Function(Rest, A0, << Acc/binary, $t >>); $U -> Function(Rest, A0, << Acc/binary, $u >>); $V -> Function(Rest, A0, << Acc/binary, $v >>); $W -> Function(Rest, A0, << Acc/binary, $w >>); $X -> Function(Rest, A0, << Acc/binary, $x >>); $Y -> Function(Rest, A0, << Acc/binary, $y >>); $Z -> Function(Rest, A0, << Acc/binary, $z >>); C -> Function(Rest, A0, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, Acc), $A -> Function(Rest, A0, A1, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, Acc), $A -> Function(Rest, A0, A1, A2, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, Acc), $A -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, C >>) ). %% INLINE_LOWERCASE_BC(Bin) %% %% Lowercase the entire binary string in a binary comprehension. -define(INLINE_LOWERCASE_BC(Bin), << << case C of $A -> $a; $B -> $b; $C -> $c; $D -> $d; $E -> $e; $F -> $f; $G -> $g; $H -> $h; $I -> $i; $J -> $j; $K -> $k; $L -> $l; $M -> $m; $N -> $n; $O -> $o; $P -> $p; $Q -> $q; $R -> $r; $S -> $s; $T -> $t; $U -> $u; $V -> $v; $W -> $w; $X -> $x; $Y -> $y; $Z -> $z; C -> C end >> || << C >> <= Bin >>). -endif. |
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
{application,plug,
[{registered,[]},
{description,"A specification and conveniences for composable modules between web applications"},
{applications,[kernel,stdlib,elixir,crypto,logger]},
{mod,{'Elixir.Plug',[]}},
{vsn,"1.0.3"},
{modules,['Elixir.Collectable.Plug.Conn',
'Elixir.Inspect.Plug.Conn','Elixir.Plug',
'Elixir.Plug.Adapters.Cowboy',
'Elixir.Plug.Adapters.Cowboy.Conn',
'Elixir.Plug.Adapters.Cowboy.Handler',
'Elixir.Plug.Adapters.Test.Conn',
'Elixir.Plug.Adapters.Translator',
'Elixir.Plug.Builder','Elixir.Plug.CSRFProtection',
'Elixir.Plug.CSRFProtection.InvalidCSRFTokenError',
'Elixir.Plug.CSRFProtection.InvalidCrossOriginRequestError',
'Elixir.Plug.Conn','Elixir.Plug.Conn.Adapter',
'Elixir.Plug.Conn.AlreadySentError',
'Elixir.Plug.Conn.Cookies',
'Elixir.Plug.Conn.InvalidHeaderError',
'Elixir.Plug.Conn.NotSentError',
'Elixir.Plug.Conn.Query','Elixir.Plug.Conn.Status',
'Elixir.Plug.Conn.Unfetched','Elixir.Plug.Conn.Utils',
'Elixir.Plug.Conn.WrapperError','Elixir.Plug.Crypto',
'Elixir.Plug.Crypto.KeyGenerator',
'Elixir.Plug.Crypto.MessageEncryptor',
'Elixir.Plug.Crypto.MessageVerifier',
'Elixir.Plug.Debugger','Elixir.Plug.ErrorHandler',
'Elixir.Plug.Exception','Elixir.Plug.Exception.Any',
'Elixir.Plug.HTML','Elixir.Plug.Head',
'Elixir.Plug.Logger','Elixir.Plug.MIME',
'Elixir.Plug.MethodOverride','Elixir.Plug.Parsers',
'Elixir.Plug.Parsers.BadEncodingError',
'Elixir.Plug.Parsers.JSON',
'Elixir.Plug.Parsers.MULTIPART',
'Elixir.Plug.Parsers.ParseError',
'Elixir.Plug.Parsers.RequestTooLargeError',
'Elixir.Plug.Parsers.URLENCODED',
'Elixir.Plug.Parsers.UnsupportedMediaTypeError',
'Elixir.Plug.RequestId','Elixir.Plug.Router',
'Elixir.Plug.Router.InvalidSpecError',
'Elixir.Plug.Router.Utils','Elixir.Plug.SSL',
'Elixir.Plug.Session','Elixir.Plug.Session.COOKIE',
'Elixir.Plug.Session.ETS','Elixir.Plug.Session.Store',
'Elixir.Plug.Static',
'Elixir.Plug.Static.InvalidPathError',
'Elixir.Plug.Supervisor','Elixir.Plug.Test',
'Elixir.Plug.Upload']}]}.
|
> > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 |
{application,ranch,
[{description,"Socket acceptor pool for TCP protocols."},
{vsn,"1.2.0"},
{id,"git"},
{modules,[ranch,ranch_acceptor,ranch_acceptors_sup,ranch_app,
ranch_conns_sup,ranch_listener_sup,ranch_protocol,
ranch_server,ranch_ssl,ranch_sup,ranch_tcp,
ranch_transport]},
{registered,[ranch_sup,ranch_server]},
{applications,[kernel,stdlib]},
{mod,{ranch_app,[]}},
{env,[]}]}.
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > | 1 2 3 4 5 6 7 |
{application,antigone,
[{registered,[]},
{description,"antigone"},
{applications,[kernel,stdlib,elixir,logger,cowboy,plug]},
{mod,{'Elixir.Antigone',[]}},
{vsn,"0.0.1"},
{modules,['Elixir.Antigone','Elixir.Router','Elixir.Static']}]}.
|
> > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{application,cowboy,
[{description,"Small, fast, modular HTTP server."},
{vsn,"1.0.4"},
{id,"git"},
{modules,[cowboy,cowboy_app,cowboy_bstr,cowboy_clock,
cowboy_handler,cowboy_http,cowboy_http_handler,
cowboy_loop_handler,cowboy_middleware,cowboy_protocol,
cowboy_req,cowboy_rest,cowboy_router,cowboy_spdy,
cowboy_static,cowboy_sub_protocol,cowboy_sup,
cowboy_websocket,cowboy_websocket_handler]},
{registered,[cowboy_clock,cowboy_sup]},
{applications,[kernel,stdlib,ranch,cowlib,crypto]},
{mod,{cowboy_app,[]}},
{env,[]}]}.
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > | 1 2 3 4 5 6 7 8 |
{application,cowlib,
[{description,"Support library for manipulating Web protocols."},
{vsn,"1.0.2"},
{id,"git"},
{modules,[cow_cookie,cow_date,cow_http,cow_http_hd,cow_http_te,
cow_mimetypes,cow_multipart,cow_qs,cow_spdy]},
{registered,[]},
{applications,[kernel,stdlib,crypto]}]}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | %% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu> %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -ifndef(COW_INLINE_HRL). -define(COW_INLINE_HRL, 1). %% INLINE_LOWERCASE(Function, Rest, Acc, ...) %% %% To be included at the end of a case block. %% Defined for up to 10 extra arguments. -define(INLINE_LOWERCASE(Function, Rest, Acc), $A -> Function(Rest, << Acc/binary, $a >>); $B -> Function(Rest, << Acc/binary, $b >>); $C -> Function(Rest, << Acc/binary, $c >>); $D -> Function(Rest, << Acc/binary, $d >>); $E -> Function(Rest, << Acc/binary, $e >>); $F -> Function(Rest, << Acc/binary, $f >>); $G -> Function(Rest, << Acc/binary, $g >>); $H -> Function(Rest, << Acc/binary, $h >>); $I -> Function(Rest, << Acc/binary, $i >>); $J -> Function(Rest, << Acc/binary, $j >>); $K -> Function(Rest, << Acc/binary, $k >>); $L -> Function(Rest, << Acc/binary, $l >>); $M -> Function(Rest, << Acc/binary, $m >>); $N -> Function(Rest, << Acc/binary, $n >>); $O -> Function(Rest, << Acc/binary, $o >>); $P -> Function(Rest, << Acc/binary, $p >>); $Q -> Function(Rest, << Acc/binary, $q >>); $R -> Function(Rest, << Acc/binary, $r >>); $S -> Function(Rest, << Acc/binary, $s >>); $T -> Function(Rest, << Acc/binary, $t >>); $U -> Function(Rest, << Acc/binary, $u >>); $V -> Function(Rest, << Acc/binary, $v >>); $W -> Function(Rest, << Acc/binary, $w >>); $X -> Function(Rest, << Acc/binary, $x >>); $Y -> Function(Rest, << Acc/binary, $y >>); $Z -> Function(Rest, << Acc/binary, $z >>); C -> Function(Rest, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, Acc), $A -> Function(Rest, A0, << Acc/binary, $a >>); $B -> Function(Rest, A0, << Acc/binary, $b >>); $C -> Function(Rest, A0, << Acc/binary, $c >>); $D -> Function(Rest, A0, << Acc/binary, $d >>); $E -> Function(Rest, A0, << Acc/binary, $e >>); $F -> Function(Rest, A0, << Acc/binary, $f >>); $G -> Function(Rest, A0, << Acc/binary, $g >>); $H -> Function(Rest, A0, << Acc/binary, $h >>); $I -> Function(Rest, A0, << Acc/binary, $i >>); $J -> Function(Rest, A0, << Acc/binary, $j >>); $K -> Function(Rest, A0, << Acc/binary, $k >>); $L -> Function(Rest, A0, << Acc/binary, $l >>); $M -> Function(Rest, A0, << Acc/binary, $m >>); $N -> Function(Rest, A0, << Acc/binary, $n >>); $O -> Function(Rest, A0, << Acc/binary, $o >>); $P -> Function(Rest, A0, << Acc/binary, $p >>); $Q -> Function(Rest, A0, << Acc/binary, $q >>); $R -> Function(Rest, A0, << Acc/binary, $r >>); $S -> Function(Rest, A0, << Acc/binary, $s >>); $T -> Function(Rest, A0, << Acc/binary, $t >>); $U -> Function(Rest, A0, << Acc/binary, $u >>); $V -> Function(Rest, A0, << Acc/binary, $v >>); $W -> Function(Rest, A0, << Acc/binary, $w >>); $X -> Function(Rest, A0, << Acc/binary, $x >>); $Y -> Function(Rest, A0, << Acc/binary, $y >>); $Z -> Function(Rest, A0, << Acc/binary, $z >>); C -> Function(Rest, A0, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, Acc), $A -> Function(Rest, A0, A1, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, Acc), $A -> Function(Rest, A0, A1, A2, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, Acc), $A -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, C >>) ). %% INLINE_LOWERCASE_BC(Bin) %% %% Lowercase the entire binary string in a binary comprehension. -define(INLINE_LOWERCASE_BC(Bin), << << case C of $A -> $a; $B -> $b; $C -> $c; $D -> $d; $E -> $e; $F -> $f; $G -> $g; $H -> $h; $I -> $i; $J -> $j; $K -> $k; $L -> $l; $M -> $m; $N -> $n; $O -> $o; $P -> $p; $Q -> $q; $R -> $r; $S -> $s; $T -> $t; $U -> $u; $V -> $v; $W -> $w; $X -> $x; $Y -> $y; $Z -> $z; C -> C end >> || << C >> <= Bin >>). -endif. |
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
{application,plug,
[{registered,[]},
{description,"A specification and conveniences for composable modules between web applications"},
{applications,[kernel,stdlib,elixir,crypto,logger]},
{mod,{'Elixir.Plug',[]}},
{vsn,"1.0.3"},
{modules,['Elixir.Collectable.Plug.Conn',
'Elixir.Inspect.Plug.Conn','Elixir.Plug',
'Elixir.Plug.Adapters.Cowboy',
'Elixir.Plug.Adapters.Cowboy.Conn',
'Elixir.Plug.Adapters.Cowboy.Handler',
'Elixir.Plug.Adapters.Test.Conn',
'Elixir.Plug.Adapters.Translator',
'Elixir.Plug.Builder','Elixir.Plug.CSRFProtection',
'Elixir.Plug.CSRFProtection.InvalidCSRFTokenError',
'Elixir.Plug.CSRFProtection.InvalidCrossOriginRequestError',
'Elixir.Plug.Conn','Elixir.Plug.Conn.Adapter',
'Elixir.Plug.Conn.AlreadySentError',
'Elixir.Plug.Conn.Cookies',
'Elixir.Plug.Conn.InvalidHeaderError',
'Elixir.Plug.Conn.NotSentError',
'Elixir.Plug.Conn.Query','Elixir.Plug.Conn.Status',
'Elixir.Plug.Conn.Unfetched','Elixir.Plug.Conn.Utils',
'Elixir.Plug.Conn.WrapperError','Elixir.Plug.Crypto',
'Elixir.Plug.Crypto.KeyGenerator',
'Elixir.Plug.Crypto.MessageEncryptor',
'Elixir.Plug.Crypto.MessageVerifier',
'Elixir.Plug.Debugger','Elixir.Plug.ErrorHandler',
'Elixir.Plug.Exception','Elixir.Plug.Exception.Any',
'Elixir.Plug.HTML','Elixir.Plug.Head',
'Elixir.Plug.Logger','Elixir.Plug.MIME',
'Elixir.Plug.MethodOverride','Elixir.Plug.Parsers',
'Elixir.Plug.Parsers.BadEncodingError',
'Elixir.Plug.Parsers.JSON',
'Elixir.Plug.Parsers.MULTIPART',
'Elixir.Plug.Parsers.ParseError',
'Elixir.Plug.Parsers.RequestTooLargeError',
'Elixir.Plug.Parsers.URLENCODED',
'Elixir.Plug.Parsers.UnsupportedMediaTypeError',
'Elixir.Plug.RequestId','Elixir.Plug.Router',
'Elixir.Plug.Router.InvalidSpecError',
'Elixir.Plug.Router.Utils','Elixir.Plug.SSL',
'Elixir.Plug.Session','Elixir.Plug.Session.COOKIE',
'Elixir.Plug.Session.ETS','Elixir.Plug.Session.Store',
'Elixir.Plug.Static',
'Elixir.Plug.Static.InvalidPathError',
'Elixir.Plug.Supervisor','Elixir.Plug.Test',
'Elixir.Plug.Upload']}]}.
|
> > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 |
{application,ranch,
[{description,"Socket acceptor pool for TCP protocols."},
{vsn,"1.2.0"},
{id,"git"},
{modules,[ranch,ranch_acceptor,ranch_acceptors_sup,ranch_app,
ranch_conns_sup,ranch_listener_sup,ranch_protocol,
ranch_server,ranch_ssl,ranch_sup,ranch_tcp,
ranch_transport]},
{registered,[ranch_sup,ranch_server]},
{applications,[kernel,stdlib]},
{mod,{ranch_app,[]}},
{env,[]}]}.
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.
# You can configure for your application as:
#
# config :antigone, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:antigone, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | Cowboy is available thanks to the work of: Loïc Hoguin Magnus Klaar Ali Sabil Anthony Ramine Adam Cammack Tom Burdick James Fish Paul Oliver Slava Yurin Vladimir Dronnikov YAMAMOTO Takashi Yurii Rashkovskii Andrew Majorov Eduardo Gurgel Egobrain Josh Toft Steven Gravell Alex Kovalev Andrew Thompson Hunter Morris Ivan Lisenkov Martin Rehfeld Matthias Endler Seletskiy Stanislav Sina Samavati Tristan Sloughter 0x00F6 0xAX Adam Cammmack Adrian Roe Alexander Færøy Alexei Sholik Alexey Lebedeff Andre Graf Andrzej Sliwa Blake Gentry Bob Ippolito Boris Faure Cameron Bytheway Cristian Hancila Daniel White Danielle Sucher Dave Peticolas David Kelly David N. Welton DeadZen Dmitry Groshev Drew Drew Varner Eiichi Tsukata Fred Hebert Hans Ulrich Niedermann Ivan Blinkov Jeremy Ong Jesper Louis Andersen Josh Allmann Josh Marchán José Valim Julian Squires Klaus Trainer Kuk-Hyun Lee Mathieu Lecarme Max Lapshin Michael Truog Michiel Hakvoort Nakai Ryosuke Ori Bar Pablo Vieytes Paulo Oliveira Peter Ericson RJ Radosław Szymczyszyn Richard Ramsden Roberto Ostinelli Sergey Prokhorov Sergey Rublev Sergey Urbanovich Seven Du Thomas Nordström Tim Dysinger Tomas Morstein Unix1 alisdair sullivan dbmercer derdesign mocchira pmyarchon rambocoder serge |
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# See LICENSE for licensing information.
PROJECT = cowboy
# Options.
ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \
+warn_shadow_vars +warn_obsolete_guard +warn_missing_spec
COMPILE_FIRST = cowboy_middleware cowboy_sub_protocol
CT_OPTS += -pa test -ct_hooks cowboy_ct_hook []
PLT_APPS = crypto public_key ssl
CI_OTP = OTP_R16B01 OTP_R16B02 OTP_R16B03-1 OTP-17.0.2 OTP-17.1.2 OTP-17.2.2 OTP-17.3.4 OTP-17.4.1 OTP-17.5.6.2 OTP-18.0.2
# Dependencies.
DEPS = cowlib ranch
TEST_DEPS = ct_helper gun
dep_ct_helper = git https://github.com/extend/ct_helper.git master
dep_gun = git https://github.com/ninenines/gun b85c1f726ca49ac0e3abdcf717317cb95b06207d
# Standard targets.
include erlang.mk
# Documentation.
dep_ezdoc = git https://github.com/ninenines/ezdoc master
$(eval $(call dep_target,ezdoc))
build-doc-deps: $(DEPS_DIR)/ezdoc
$(MAKE) -C $(DEPS_DIR)/ezdoc
define ezdoc_script
io:format("Building manual~n"),
[begin
AST = ezdoc:parse_file(F),
BF = filename:rootname(filename:basename(F)),
io:format(" ~s~n", [BF]),
file:write_file("doc/markdown/manual/" ++ BF ++ ".md", ezdoc_markdown:export(AST)),
case BF of
"cowboy" ++ _ when BF =/= "cowboy_app" ->
file:write_file("doc/man3/" ++ BF ++ ".3", ezdoc_man:export(3, AST));
_ when BF =/= "index" ->
file:write_file("doc/man7/" ++ BF ++ ".7", ezdoc_man:export(7, AST));
_ ->
ok
end
end || F <- filelib:wildcard("doc/src/manual/*.ezdoc")],
io:format("Building guide~n"),
[begin
AST = ezdoc:parse_file(F),
BF = filename:rootname(filename:basename(F)),
io:format(" ~s~n", [BF]),
file:write_file("doc/markdown/guide/" ++ BF ++ ".md", ezdoc_markdown:export(AST))
end || F <- filelib:wildcard("doc/src/guide/*.ezdoc")],
io:format("Done.~n"),
init:stop().
endef
export ezdoc_script
docs:: clean-docs build-doc-deps
@mkdir -p doc/man3 doc/man7 doc/markdown/guide doc/markdown/manual
$(gen_verbose) erl -noinput -pa ebin deps/ezdoc/ebin -eval "$$ezdoc_script"
@gzip doc/man3/*.3 doc/man7/*.7
@cp doc/src/guide/*.png doc/markdown/guide
clean-docs:
$(gen_verbose) rm -rf doc/man3 doc/man7 doc/markdown
MAN_INSTALL_PATH ?= /usr/local/share/man
install-docs:
mkdir -p $(MAN_INSTALL_PATH)/man3/ $(MAN_INSTALL_PATH)/man7/
install -g 0 -o 0 -m 0644 doc/man3/*.gz $(MAN_INSTALL_PATH)/man3/
install -g 0 -o 0 -m 0644 doc/man7/*.gz $(MAN_INSTALL_PATH)/man7/
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | Cowboy ====== Cowboy is a small, fast and modular HTTP server written in Erlang. Goals ----- Cowboy aims to provide a **complete** HTTP stack in a **small** code base. It is optimized for **low latency** and **low memory usage**, in part because it uses **binary strings**. Cowboy provides **routing** capabilities, selectively dispatching requests to handlers written in Erlang. Because it uses Ranch for managing connections, Cowboy can easily be **embedded** in any other application. No parameterized module. No process dictionary. **Clean** Erlang code. Sponsors -------- The SPDY implementation was sponsored by [LeoFS Cloud Storage](http://www.leofs.org). The project is currently sponsored by [Kato.im](https://kato.im). Online documentation -------------------- * [User guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide) * [Function reference](http://ninenines.eu/docs/en/cowboy/HEAD/manual) Offline documentation --------------------- * While still online, run `make docs` * Function reference man pages available in `doc/man3/` and `doc/man7/` * Run `make install-docs` to install man pages on your system * Full documentation in Markdown available in `doc/markdown/` * Examples available in `examples/` Getting help ------------ * Official IRC Channel: #ninenines on irc.freenode.net * [Mailing Lists](http://lists.ninenines.eu) * [Commercial Support](http://ninenines.eu/support) |
> > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{application,cowboy,
[{description,"Small, fast, modular HTTP server."},
{vsn,"1.0.4"},
{id,"git"},
{modules,[cowboy,cowboy_app,cowboy_bstr,cowboy_clock,
cowboy_handler,cowboy_http,cowboy_http_handler,
cowboy_loop_handler,cowboy_middleware,cowboy_protocol,
cowboy_req,cowboy_rest,cowboy_router,cowboy_spdy,
cowboy_static,cowboy_sub_protocol,cowboy_sup,
cowboy_websocket,cowboy_websocket_handler]},
{registered,[cowboy_clock,cowboy_sup]},
{applications,[kernel,stdlib,ranch,cowlib,crypto]},
{mod,{cowboy_app,[]}},
{env,[]}]}.
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 |
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.PHONY: all app deps search rel docs install-docs check tests clean distclean help erlang-mk
ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST)))
ERLANG_MK_VERSION = 1.2.0-632-g26ea355-dirty
# Core configuration.
PROJECT ?= $(notdir $(CURDIR))
PROJECT := $(strip $(PROJECT))
PROJECT_VERSION ?= rolling
# Verbosity.
V ?= 0
verbose_0 = @
verbose = $(verbose_$(V))
gen_verbose_0 = @echo " GEN " $@;
gen_verbose = $(gen_verbose_$(V))
# Temporary files directory.
ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk
export ERLANG_MK_TMP
# "erl" command.
ERL = erl +A0 -noinput -boot start_clean
# Platform detection.
# @todo Add Windows/Cygwin detection eventually.
ifeq ($(PLATFORM),)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
PLATFORM = linux
else ifeq ($(UNAME_S),Darwin)
PLATFORM = darwin
else ifeq ($(UNAME_S),SunOS)
PLATFORM = solaris
else ifeq ($(UNAME_S),GNU)
PLATFORM = gnu
else ifeq ($(UNAME_S),FreeBSD)
PLATFORM = freebsd
else ifeq ($(UNAME_S),NetBSD)
PLATFORM = netbsd
else ifeq ($(UNAME_S),OpenBSD)
PLATFORM = openbsd
else
$(error Unable to detect platform. Please open a ticket with the output of uname -a.)
endif
export PLATFORM
endif
# Core targets.
ifneq ($(words $(MAKECMDGOALS)),1)
.NOTPARALLEL:
endif
all:: deps
$(verbose) $(MAKE) --no-print-directory app
$(verbose) $(MAKE) --no-print-directory rel
# Noop to avoid a Make warning when there's nothing to do.
rel::
$(verbose) echo -n
check:: clean app tests
clean:: clean-crashdump
clean-crashdump:
ifneq ($(wildcard erl_crash.dump),)
$(gen_verbose) rm -f erl_crash.dump
endif
distclean:: clean
help::
$(verbose) printf "%s\n" \
"erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \
"Copyright (c) 2013-2015 Loïc Hoguin <essen@ninenines.eu>" \
"" \
"Usage: [V=1] $(MAKE) [-jNUM] [target]..." \
"" \
"Core targets:" \
" all Run deps, app and rel targets in that order" \
" app Compile the project" \
" deps Fetch dependencies (if needed) and compile them" \
" search q=... Search for a package in the built-in index" \
" rel Build a release for this project, if applicable" \
" docs Build the documentation for this project" \
" install-docs Install the man pages for this project" \
" check Compile and run all tests and analysis for this project" \
" tests Run the tests for this project" \
" clean Delete temporary and output files from most targets" \
" distclean Delete all temporary and output files" \
" help Display this help and exit" \
" erlang-mk Update erlang.mk to the latest version"
# Core functions.
empty :=
space := $(empty) $(empty)
tab := $(empty) $(empty)
comma := ,
define newline
endef
define comma_list
$(subst $(space),$(comma),$(strip $(1)))
endef
# Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy.
define erlang
$(ERL) -pz $(ERLANG_MK_TMP)/rebar/ebin -eval "$(subst $(newline),,$(subst ",\",$(1)))" -- erlang.mk
endef
ifeq ($(shell which wget 2>/dev/null | wc -l), 1)
define core_http_get
wget --no-check-certificate -O $(1) $(2)|| rm $(1)
endef
else
define core_http_get.erl
ssl:start(),
inets:start(),
case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of
{ok, {{_, 200, _}, _, Body}} ->
case file:write_file("$(1)", Body) of
ok -> ok;
{error, R1} -> halt(R1)
end;
{error, R2} ->
halt(R2)
end,
halt(0).
endef
define core_http_get
$(call erlang,$(call core_http_get.erl,$(1),$(2)))
endef
endif
core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
core_find = $(foreach d,$(call core_ls,$1*),$(call core_find,$d/,$2) $(filter $(subst *,%,$2),$d))
core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))
# @todo On Windows: $(shell dir /B $(1)); make sure to handle when no file exists.
core_ls = $(filter-out $(1),$(shell echo $(1)))
# Automated update.
ERLANG_MK_BUILD_CONFIG ?= build.config
ERLANG_MK_BUILD_DIR ?= .erlang.mk.build
erlang-mk:
git clone https://github.com/ninenines/erlang.mk $(ERLANG_MK_BUILD_DIR)
if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR); fi
cd $(ERLANG_MK_BUILD_DIR) && $(MAKE)
cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
rm -rf $(ERLANG_MK_BUILD_DIR)
# The erlang.mk package index is bundled in the default erlang.mk build.
# Search for the string "copyright" to skip to the rest of the code.
PACKAGES += aberth
pkg_aberth_name = aberth
pkg_aberth_description = Generic BERT-RPC server in Erlang
pkg_aberth_homepage = https://github.com/a13x/aberth
pkg_aberth_fetch = git
pkg_aberth_repo = https://github.com/a13x/aberth
pkg_aberth_commit = master
PACKAGES += active
pkg_active_name = active
pkg_active_description = Active development for Erlang: rebuild and reload source/binary files while the VM is running
pkg_active_homepage = https://github.com/proger/active
pkg_active_fetch = git
pkg_active_repo = https://github.com/proger/active
pkg_active_commit = master
PACKAGES += actordb_core
pkg_actordb_core_name = actordb_core
pkg_actordb_core_description = ActorDB main source
pkg_actordb_core_homepage = http://www.actordb.com/
pkg_actordb_core_fetch = git
pkg_actordb_core_repo = https://github.com/biokoda/actordb_core
pkg_actordb_core_commit = master
PACKAGES += actordb_thrift
pkg_actordb_thrift_name = actordb_thrift
pkg_actordb_thrift_description = Thrift API for ActorDB
pkg_actordb_thrift_homepage = http://www.actordb.com/
pkg_actordb_thrift_fetch = git
pkg_actordb_thrift_repo = https://github.com/biokoda/actordb_thrift
pkg_actordb_thrift_commit = master
PACKAGES += aleppo
pkg_aleppo_name = aleppo
pkg_aleppo_description = Alternative Erlang Pre-Processor
pkg_aleppo_homepage = https://github.com/ErlyORM/aleppo
pkg_aleppo_fetch = git
pkg_aleppo_repo = https://github.com/ErlyORM/aleppo
pkg_aleppo_commit = master
PACKAGES += alog
pkg_alog_name = alog
pkg_alog_description = Simply the best logging framework for Erlang
pkg_alog_homepage = https://github.com/siberian-fast-food/alogger
pkg_alog_fetch = git
pkg_alog_repo = https://github.com/siberian-fast-food/alogger
pkg_alog_commit = master
PACKAGES += amqp_client
pkg_amqp_client_name = amqp_client
pkg_amqp_client_description = RabbitMQ Erlang AMQP client
pkg_amqp_client_homepage = https://www.rabbitmq.com/erlang-client-user-guide.html
pkg_amqp_client_fetch = git
pkg_amqp_client_repo = https://github.com/rabbitmq/rabbitmq-erlang-client.git
pkg_amqp_client_commit = master
PACKAGES += annotations
pkg_annotations_name = annotations
pkg_annotations_description = Simple code instrumentation utilities
pkg_annotations_homepage = https://github.com/hyperthunk/annotations
pkg_annotations_fetch = git
pkg_annotations_repo = https://github.com/hyperthunk/annotations
pkg_annotations_commit = master
PACKAGES += antidote
pkg_antidote_name = antidote
pkg_antidote_description = Large-scale computation without synchronisation
pkg_antidote_homepage = https://syncfree.lip6.fr/
pkg_antidote_fetch = git
pkg_antidote_repo = https://github.com/SyncFree/antidote
pkg_antidote_commit = master
PACKAGES += apns
pkg_apns_name = apns
pkg_apns_description = Apple Push Notification Server for Erlang
pkg_apns_homepage = http://inaka.github.com/apns4erl
pkg_apns_fetch = git
pkg_apns_repo = https://github.com/inaka/apns4erl
pkg_apns_commit = 1.0.4
PACKAGES += azdht
pkg_azdht_name = azdht
pkg_azdht_description = Azureus Distributed Hash Table (DHT) in Erlang
pkg_azdht_homepage = https://github.com/arcusfelis/azdht
pkg_azdht_fetch = git
pkg_azdht_repo = https://github.com/arcusfelis/azdht
pkg_azdht_commit = master
PACKAGES += backoff
pkg_backoff_name = backoff
pkg_backoff_description = Simple exponential backoffs in Erlang
pkg_backoff_homepage = https://github.com/ferd/backoff
pkg_backoff_fetch = git
pkg_backoff_repo = https://github.com/ferd/backoff
pkg_backoff_commit = master
PACKAGES += barrel_tcp
pkg_barrel_tcp_name = barrel_tcp
pkg_barrel_tcp_description = barrel is a generic TCP acceptor pool with low latency in Erlang.
pkg_barrel_tcp_homepage = https://github.com/benoitc-attic/barrel_tcp
pkg_barrel_tcp_fetch = git
pkg_barrel_tcp_repo = https://github.com/benoitc-attic/barrel_tcp
pkg_barrel_tcp_commit = master
PACKAGES += basho_bench
pkg_basho_bench_name = basho_bench
pkg_basho_bench_description = A load-generation and testing tool for basically whatever you can write a returning Erlang function for.
pkg_basho_bench_homepage = https://github.com/basho/basho_bench
pkg_basho_bench_fetch = git
pkg_basho_bench_repo = https://github.com/basho/basho_bench
pkg_basho_bench_commit = master
PACKAGES += bcrypt
pkg_bcrypt_name = bcrypt
pkg_bcrypt_description = Bcrypt Erlang / C library
pkg_bcrypt_homepage = https://github.com/riverrun/branglecrypt
pkg_bcrypt_fetch = git
pkg_bcrypt_repo = https://github.com/riverrun/branglecrypt
pkg_bcrypt_commit = master
PACKAGES += beam
pkg_beam_name = beam
pkg_beam_description = BEAM emulator written in Erlang
pkg_beam_homepage = https://github.com/tonyrog/beam
pkg_beam_fetch = git
pkg_beam_repo = https://github.com/tonyrog/beam
pkg_beam_commit = master
PACKAGES += beanstalk
pkg_beanstalk_name = beanstalk
pkg_beanstalk_description = An Erlang client for beanstalkd
pkg_beanstalk_homepage = https://github.com/tim/erlang-beanstalk
pkg_beanstalk_fetch = git
pkg_beanstalk_repo = https://github.com/tim/erlang-beanstalk
pkg_beanstalk_commit = master
PACKAGES += bear
pkg_bear_name = bear
pkg_bear_description = a set of statistics functions for erlang
pkg_bear_homepage = https://github.com/boundary/bear
pkg_bear_fetch = git
pkg_bear_repo = https://github.com/boundary/bear
pkg_bear_commit = master
PACKAGES += bertconf
pkg_bertconf_name = bertconf
pkg_bertconf_description = Make ETS tables out of statc BERT files that are auto-reloaded
pkg_bertconf_homepage = https://github.com/ferd/bertconf
pkg_bertconf_fetch = git
pkg_bertconf_repo = https://github.com/ferd/bertconf
pkg_bertconf_commit = master
PACKAGES += bifrost
pkg_bifrost_name = bifrost
pkg_bifrost_description = Erlang FTP Server Framework
pkg_bifrost_homepage = https://github.com/thorstadt/bifrost
pkg_bifrost_fetch = git
pkg_bifrost_repo = https://github.com/thorstadt/bifrost
pkg_bifrost_commit = master
PACKAGES += binpp
pkg_binpp_name = binpp
pkg_binpp_description = Erlang Binary Pretty Printer
pkg_binpp_homepage = https://github.com/jtendo/binpp
pkg_binpp_fetch = git
pkg_binpp_repo = https://github.com/jtendo/binpp
pkg_binpp_commit = master
PACKAGES += bisect
pkg_bisect_name = bisect
pkg_bisect_description = Ordered fixed-size binary dictionary in Erlang
pkg_bisect_homepage = https://github.com/knutin/bisect
pkg_bisect_fetch = git
pkg_bisect_repo = https://github.com/knutin/bisect
pkg_bisect_commit = master
PACKAGES += bitcask
pkg_bitcask_name = bitcask
pkg_bitcask_description = because you need another a key/value storage engine
pkg_bitcask_homepage = https://github.com/basho/bitcask
pkg_bitcask_fetch = git
pkg_bitcask_repo = https://github.com/basho/bitcask
pkg_bitcask_commit = master
PACKAGES += bitstore
pkg_bitstore_name = bitstore
pkg_bitstore_description = A document based ontology development environment
pkg_bitstore_homepage = https://github.com/bdionne/bitstore
pkg_bitstore_fetch = git
pkg_bitstore_repo = https://github.com/bdionne/bitstore
pkg_bitstore_commit = master
PACKAGES += bootstrap
pkg_bootstrap_name = bootstrap
pkg_bootstrap_description = A simple, yet powerful Erlang cluster bootstrapping application.
pkg_bootstrap_homepage = https://github.com/schlagert/bootstrap
pkg_bootstrap_fetch = git
pkg_bootstrap_repo = https://github.com/schlagert/bootstrap
pkg_bootstrap_commit = master
PACKAGES += boss_db
pkg_boss_db_name = boss_db
pkg_boss_db_description = BossDB: a sharded, caching, pooling, evented ORM for Erlang
pkg_boss_db_homepage = https://github.com/ErlyORM/boss_db
pkg_boss_db_fetch = git
pkg_boss_db_repo = https://github.com/ErlyORM/boss_db
pkg_boss_db_commit = master
PACKAGES += boss
pkg_boss_name = boss
pkg_boss_description = Erlang web MVC, now featuring Comet
pkg_boss_homepage = https://github.com/ChicagoBoss/ChicagoBoss
pkg_boss_fetch = git
pkg_boss_repo = https://github.com/ChicagoBoss/ChicagoBoss
pkg_boss_commit = master
PACKAGES += bson
pkg_bson_name = bson
pkg_bson_description = BSON documents in Erlang, see bsonspec.org
pkg_bson_homepage = https://github.com/comtihon/bson-erlang
pkg_bson_fetch = git
pkg_bson_repo = https://github.com/comtihon/bson-erlang
pkg_bson_commit = master
PACKAGES += bullet
pkg_bullet_name = bullet
pkg_bullet_description = Simple, reliable, efficient streaming for Cowboy.
pkg_bullet_homepage = http://ninenines.eu
pkg_bullet_fetch = git
pkg_bullet_repo = https://github.com/extend/bullet
pkg_bullet_commit = master
PACKAGES += cache
pkg_cache_name = cache
pkg_cache_description = Erlang in-memory cache
pkg_cache_homepage = https://github.com/fogfish/cache
pkg_cache_fetch = git
pkg_cache_repo = https://github.com/fogfish/cache
pkg_cache_commit = master
PACKAGES += cake
pkg_cake_name = cake
pkg_cake_description = Really simple terminal colorization
pkg_cake_homepage = https://github.com/darach/cake-erl
pkg_cake_fetch = git
pkg_cake_repo = https://github.com/darach/cake-erl
pkg_cake_commit = v0.1.2
PACKAGES += carotene
pkg_carotene_name = carotene
pkg_carotene_description = Real-time server
pkg_carotene_homepage = https://github.com/carotene/carotene
pkg_carotene_fetch = git
pkg_carotene_repo = https://github.com/carotene/carotene
pkg_carotene_commit = master
PACKAGES += cberl
pkg_cberl_name = cberl
pkg_cberl_description = NIF based Erlang bindings for Couchbase
pkg_cberl_homepage = https://github.com/chitika/cberl
pkg_cberl_fetch = git
pkg_cberl_repo = https://github.com/chitika/cberl
pkg_cberl_commit = master
PACKAGES += cecho
pkg_cecho_name = cecho
pkg_cecho_description = An ncurses library for Erlang
pkg_cecho_homepage = https://github.com/mazenharake/cecho
pkg_cecho_fetch = git
pkg_cecho_repo = https://github.com/mazenharake/cecho
pkg_cecho_commit = master
PACKAGES += cferl
pkg_cferl_name = cferl
pkg_cferl_description = Rackspace / Open Stack Cloud Files Erlang Client
pkg_cferl_homepage = https://github.com/ddossot/cferl
pkg_cferl_fetch = git
pkg_cferl_repo = https://github.com/ddossot/cferl
pkg_cferl_commit = master
PACKAGES += chaos_monkey
pkg_chaos_monkey_name = chaos_monkey
pkg_chaos_monkey_description = This is The CHAOS MONKEY. It will kill your processes.
pkg_chaos_monkey_homepage = https://github.com/dLuna/chaos_monkey
pkg_chaos_monkey_fetch = git
pkg_chaos_monkey_repo = https://github.com/dLuna/chaos_monkey
pkg_chaos_monkey_commit = master
PACKAGES += check_node
pkg_check_node_name = check_node
pkg_check_node_description = Nagios Scripts for monitoring Riak
pkg_check_node_homepage = https://github.com/basho-labs/riak_nagios
pkg_check_node_fetch = git
pkg_check_node_repo = https://github.com/basho-labs/riak_nagios
pkg_check_node_commit = master
PACKAGES += chronos
pkg_chronos_name = chronos
pkg_chronos_description = Timer module for Erlang that makes it easy to abstact time out of the tests.
pkg_chronos_homepage = https://github.com/lehoff/chronos
pkg_chronos_fetch = git
pkg_chronos_repo = https://github.com/lehoff/chronos
pkg_chronos_commit = master
PACKAGES += classifier
pkg_classifier_name = classifier
pkg_classifier_description = An Erlang Bayesian Filter and Text Classifier
pkg_classifier_homepage = https://github.com/inaka/classifier
pkg_classifier_fetch = git
pkg_classifier_repo = https://github.com/inaka/classifier
pkg_classifier_commit = master
PACKAGES += clique
pkg_clique_name = clique
pkg_clique_description = CLI Framework for Erlang
pkg_clique_homepage = https://github.com/basho/clique
pkg_clique_fetch = git
pkg_clique_repo = https://github.com/basho/clique
pkg_clique_commit = develop
PACKAGES += cl
pkg_cl_name = cl
pkg_cl_description = OpenCL binding for Erlang
pkg_cl_homepage = https://github.com/tonyrog/cl
pkg_cl_fetch = git
pkg_cl_repo = https://github.com/tonyrog/cl
pkg_cl_commit = master
PACKAGES += cloudi_core
pkg_cloudi_core_name = cloudi_core
pkg_cloudi_core_description = CloudI internal service runtime
pkg_cloudi_core_homepage = http://cloudi.org/
pkg_cloudi_core_fetch = git
pkg_cloudi_core_repo = https://github.com/CloudI/cloudi_core
pkg_cloudi_core_commit = master
PACKAGES += cloudi_service_api_requests
pkg_cloudi_service_api_requests_name = cloudi_service_api_requests
pkg_cloudi_service_api_requests_description = CloudI Service API requests (JSON-RPC/Erlang-term support)
pkg_cloudi_service_api_requests_homepage = http://cloudi.org/
pkg_cloudi_service_api_requests_fetch = git
pkg_cloudi_service_api_requests_repo = https://github.com/CloudI/cloudi_service_api_requests
pkg_cloudi_service_api_requests_commit = master
PACKAGES += cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_name = cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_description = Cassandra CQL CloudI Service
pkg_cloudi_service_db_cassandra_cql_homepage = http://cloudi.org/
pkg_cloudi_service_db_cassandra_cql_fetch = git
pkg_cloudi_service_db_cassandra_cql_repo = https://github.com/CloudI/cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_commit = master
PACKAGES += cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_name = cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_description = Cassandra CloudI Service
pkg_cloudi_service_db_cassandra_homepage = http://cloudi.org/
pkg_cloudi_service_db_cassandra_fetch = git
pkg_cloudi_service_db_cassandra_repo = https://github.com/CloudI/cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_commit = master
PACKAGES += cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_name = cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_description = CouchDB CloudI Service
pkg_cloudi_service_db_couchdb_homepage = http://cloudi.org/
pkg_cloudi_service_db_couchdb_fetch = git
pkg_cloudi_service_db_couchdb_repo = https://github.com/CloudI/cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_commit = master
PACKAGES += cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_name = cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_description = elasticsearch CloudI Service
pkg_cloudi_service_db_elasticsearch_homepage = http://cloudi.org/
pkg_cloudi_service_db_elasticsearch_fetch = git
pkg_cloudi_service_db_elasticsearch_repo = https://github.com/CloudI/cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_commit = master
PACKAGES += cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_name = cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_description = memcached CloudI Service
pkg_cloudi_service_db_memcached_homepage = http://cloudi.org/
pkg_cloudi_service_db_memcached_fetch = git
pkg_cloudi_service_db_memcached_repo = https://github.com/CloudI/cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_commit = master
PACKAGES += cloudi_service_db
pkg_cloudi_service_db_name = cloudi_service_db
pkg_cloudi_service_db_description = CloudI Database (in-memory/testing/generic)
pkg_cloudi_service_db_homepage = http://cloudi.org/
pkg_cloudi_service_db_fetch = git
pkg_cloudi_service_db_repo = https://github.com/CloudI/cloudi_service_db
pkg_cloudi_service_db_commit = master
PACKAGES += cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_name = cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_description = MySQL CloudI Service
pkg_cloudi_service_db_mysql_homepage = http://cloudi.org/
pkg_cloudi_service_db_mysql_fetch = git
pkg_cloudi_service_db_mysql_repo = https://github.com/CloudI/cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_commit = master
PACKAGES += cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_name = cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_description = PostgreSQL CloudI Service
pkg_cloudi_service_db_pgsql_homepage = http://cloudi.org/
pkg_cloudi_service_db_pgsql_fetch = git
pkg_cloudi_service_db_pgsql_repo = https://github.com/CloudI/cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_commit = master
PACKAGES += cloudi_service_db_riak
pkg_cloudi_service_db_riak_name = cloudi_service_db_riak
pkg_cloudi_service_db_riak_description = Riak CloudI Service
pkg_cloudi_service_db_riak_homepage = http://cloudi.org/
pkg_cloudi_service_db_riak_fetch = git
pkg_cloudi_service_db_riak_repo = https://github.com/CloudI/cloudi_service_db_riak
pkg_cloudi_service_db_riak_commit = master
PACKAGES += cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_name = cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_description = Tokyo Tyrant CloudI Service
pkg_cloudi_service_db_tokyotyrant_homepage = http://cloudi.org/
pkg_cloudi_service_db_tokyotyrant_fetch = git
pkg_cloudi_service_db_tokyotyrant_repo = https://github.com/CloudI/cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_commit = master
PACKAGES += cloudi_service_filesystem
pkg_cloudi_service_filesystem_name = cloudi_service_filesystem
pkg_cloudi_service_filesystem_description = Filesystem CloudI Service
pkg_cloudi_service_filesystem_homepage = http://cloudi.org/
pkg_cloudi_service_filesystem_fetch = git
pkg_cloudi_service_filesystem_repo = https://github.com/CloudI/cloudi_service_filesystem
pkg_cloudi_service_filesystem_commit = master
PACKAGES += cloudi_service_http_client
pkg_cloudi_service_http_client_name = cloudi_service_http_client
pkg_cloudi_service_http_client_description = HTTP client CloudI Service
pkg_cloudi_service_http_client_homepage = http://cloudi.org/
pkg_cloudi_service_http_client_fetch = git
pkg_cloudi_service_http_client_repo = https://github.com/CloudI/cloudi_service_http_client
pkg_cloudi_service_http_client_commit = master
PACKAGES += cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_name = cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_description = cowboy HTTP/HTTPS CloudI Service
pkg_cloudi_service_http_cowboy_homepage = http://cloudi.org/
pkg_cloudi_service_http_cowboy_fetch = git
pkg_cloudi_service_http_cowboy_repo = https://github.com/CloudI/cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_commit = master
PACKAGES += cloudi_service_http_elli
pkg_cloudi_service_http_elli_name = cloudi_service_http_elli
pkg_cloudi_service_http_elli_description = elli HTTP CloudI Service
pkg_cloudi_service_http_elli_homepage = http://cloudi.org/
pkg_cloudi_service_http_elli_fetch = git
pkg_cloudi_service_http_elli_repo = https://github.com/CloudI/cloudi_service_http_elli
pkg_cloudi_service_http_elli_commit = master
PACKAGES += cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_name = cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_description = Map/Reduce CloudI Service
pkg_cloudi_service_map_reduce_homepage = http://cloudi.org/
pkg_cloudi_service_map_reduce_fetch = git
pkg_cloudi_service_map_reduce_repo = https://github.com/CloudI/cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_commit = master
PACKAGES += cloudi_service_oauth1
pkg_cloudi_service_oauth1_name = cloudi_service_oauth1
pkg_cloudi_service_oauth1_description = OAuth v1.0 CloudI Service
pkg_cloudi_service_oauth1_homepage = http://cloudi.org/
pkg_cloudi_service_oauth1_fetch = git
pkg_cloudi_service_oauth1_repo = https://github.com/CloudI/cloudi_service_oauth1
pkg_cloudi_service_oauth1_commit = master
PACKAGES += cloudi_service_queue
pkg_cloudi_service_queue_name = cloudi_service_queue
pkg_cloudi_service_queue_description = Persistent Queue Service
pkg_cloudi_service_queue_homepage = http://cloudi.org/
pkg_cloudi_service_queue_fetch = git
pkg_cloudi_service_queue_repo = https://github.com/CloudI/cloudi_service_queue
pkg_cloudi_service_queue_commit = master
PACKAGES += cloudi_service_quorum
pkg_cloudi_service_quorum_name = cloudi_service_quorum
pkg_cloudi_service_quorum_description = CloudI Quorum Service
pkg_cloudi_service_quorum_homepage = http://cloudi.org/
pkg_cloudi_service_quorum_fetch = git
pkg_cloudi_service_quorum_repo = https://github.com/CloudI/cloudi_service_quorum
pkg_cloudi_service_quorum_commit = master
PACKAGES += cloudi_service_router
pkg_cloudi_service_router_name = cloudi_service_router
pkg_cloudi_service_router_description = CloudI Router Service
pkg_cloudi_service_router_homepage = http://cloudi.org/
pkg_cloudi_service_router_fetch = git
pkg_cloudi_service_router_repo = https://github.com/CloudI/cloudi_service_router
pkg_cloudi_service_router_commit = master
PACKAGES += cloudi_service_tcp
pkg_cloudi_service_tcp_name = cloudi_service_tcp
pkg_cloudi_service_tcp_description = TCP CloudI Service
pkg_cloudi_service_tcp_homepage = http://cloudi.org/
pkg_cloudi_service_tcp_fetch = git
pkg_cloudi_service_tcp_repo = https://github.com/CloudI/cloudi_service_tcp
pkg_cloudi_service_tcp_commit = master
PACKAGES += cloudi_service_timers
pkg_cloudi_service_timers_name = cloudi_service_timers
pkg_cloudi_service_timers_description = Timers CloudI Service
pkg_cloudi_service_timers_homepage = http://cloudi.org/
pkg_cloudi_service_timers_fetch = git
pkg_cloudi_service_timers_repo = https://github.com/CloudI/cloudi_service_timers
pkg_cloudi_service_timers_commit = master
PACKAGES += cloudi_service_udp
pkg_cloudi_service_udp_name = cloudi_service_udp
pkg_cloudi_service_udp_description = UDP CloudI Service
pkg_cloudi_service_udp_homepage = http://cloudi.org/
pkg_cloudi_service_udp_fetch = git
pkg_cloudi_service_udp_repo = https://github.com/CloudI/cloudi_service_udp
pkg_cloudi_service_udp_commit = master
PACKAGES += cloudi_service_validate
pkg_cloudi_service_validate_name = cloudi_service_validate
pkg_cloudi_service_validate_description = CloudI Validate Service
pkg_cloudi_service_validate_homepage = http://cloudi.org/
pkg_cloudi_service_validate_fetch = git
pkg_cloudi_service_validate_repo = https://github.com/CloudI/cloudi_service_validate
pkg_cloudi_service_validate_commit = master
PACKAGES += cloudi_service_zeromq
pkg_cloudi_service_zeromq_name = cloudi_service_zeromq
pkg_cloudi_service_zeromq_description = ZeroMQ CloudI Service
pkg_cloudi_service_zeromq_homepage = http://cloudi.org/
pkg_cloudi_service_zeromq_fetch = git
pkg_cloudi_service_zeromq_repo = https://github.com/CloudI/cloudi_service_zeromq
pkg_cloudi_service_zeromq_commit = master
PACKAGES += cluster_info
pkg_cluster_info_name = cluster_info
pkg_cluster_info_description = Fork of Hibari's nifty cluster_info OTP app
pkg_cluster_info_homepage = https://github.com/basho/cluster_info
pkg_cluster_info_fetch = git
pkg_cluster_info_repo = https://github.com/basho/cluster_info
pkg_cluster_info_commit = master
PACKAGES += color
pkg_color_name = color
pkg_color_description = ANSI colors for your Erlang
pkg_color_homepage = https://github.com/julianduque/erlang-color
pkg_color_fetch = git
pkg_color_repo = https://github.com/julianduque/erlang-color
pkg_color_commit = master
PACKAGES += confetti
pkg_confetti_name = confetti
pkg_confetti_description = Erlang configuration provider / application:get_env/2 on steroids
pkg_confetti_homepage = https://github.com/jtendo/confetti
pkg_confetti_fetch = git
pkg_confetti_repo = https://github.com/jtendo/confetti
pkg_confetti_commit = master
PACKAGES += couchbeam
pkg_couchbeam_name = couchbeam
pkg_couchbeam_description = Apache CouchDB client in Erlang
pkg_couchbeam_homepage = https://github.com/benoitc/couchbeam
pkg_couchbeam_fetch = git
pkg_couchbeam_repo = https://github.com/benoitc/couchbeam
pkg_couchbeam_commit = master
PACKAGES += couch
pkg_couch_name = couch
pkg_couch_description = A embeddable document oriented database compatible with Apache CouchDB
pkg_couch_homepage = https://github.com/benoitc/opencouch
pkg_couch_fetch = git
pkg_couch_repo = https://github.com/benoitc/opencouch
pkg_couch_commit = master
PACKAGES += covertool
pkg_covertool_name = covertool
pkg_covertool_description = Tool to convert Erlang cover data files into Cobertura XML reports
pkg_covertool_homepage = https://github.com/idubrov/covertool
pkg_covertool_fetch = git
pkg_covertool_repo = https://github.com/idubrov/covertool
pkg_covertool_commit = master
PACKAGES += cowboy
pkg_cowboy_name = cowboy
pkg_cowboy_description = Small, fast and modular HTTP server.
pkg_cowboy_homepage = http://ninenines.eu
pkg_cowboy_fetch = git
pkg_cowboy_repo = https://github.com/ninenines/cowboy
pkg_cowboy_commit = 1.0.1
PACKAGES += cowdb
pkg_cowdb_name = cowdb
pkg_cowdb_description = Pure Key/Value database library for Erlang Applications
pkg_cowdb_homepage = https://github.com/refuge/cowdb
pkg_cowdb_fetch = git
pkg_cowdb_repo = https://github.com/refuge/cowdb
pkg_cowdb_commit = master
PACKAGES += cowlib
pkg_cowlib_name = cowlib
pkg_cowlib_description = Support library for manipulating Web protocols.
pkg_cowlib_homepage = http://ninenines.eu
pkg_cowlib_fetch = git
pkg_cowlib_repo = https://github.com/ninenines/cowlib
pkg_cowlib_commit = 1.0.1
PACKAGES += cpg
pkg_cpg_name = cpg
pkg_cpg_description = CloudI Process Groups
pkg_cpg_homepage = https://github.com/okeuday/cpg
pkg_cpg_fetch = git
pkg_cpg_repo = https://github.com/okeuday/cpg
pkg_cpg_commit = master
PACKAGES += cqerl
pkg_cqerl_name = cqerl
pkg_cqerl_description = Native Erlang CQL client for Cassandra
pkg_cqerl_homepage = https://matehat.github.io/cqerl/
pkg_cqerl_fetch = git
pkg_cqerl_repo = https://github.com/matehat/cqerl
pkg_cqerl_commit = master
PACKAGES += cr
pkg_cr_name = cr
pkg_cr_description = Chain Replication
pkg_cr_homepage = https://synrc.com/apps/cr/doc/cr.htm
pkg_cr_fetch = git
pkg_cr_repo = https://github.com/spawnproc/cr
pkg_cr_commit = master
PACKAGES += cuttlefish
pkg_cuttlefish_name = cuttlefish
pkg_cuttlefish_description = never lose your childlike sense of wonder baby cuttlefish, promise me?
pkg_cuttlefish_homepage = https://github.com/basho/cuttlefish
pkg_cuttlefish_fetch = git
pkg_cuttlefish_repo = https://github.com/basho/cuttlefish
pkg_cuttlefish_commit = master
PACKAGES += damocles
pkg_damocles_name = damocles
pkg_damocles_description = Erlang library for generating adversarial network conditions for QAing distributed applications/systems on a single Linux box.
pkg_damocles_homepage = https://github.com/lostcolony/damocles
pkg_damocles_fetch = git
pkg_damocles_repo = https://github.com/lostcolony/damocles
pkg_damocles_commit = master
PACKAGES += debbie
pkg_debbie_name = debbie
pkg_debbie_description = .DEB Built In Erlang
pkg_debbie_homepage = https://github.com/crownedgrouse/debbie
pkg_debbie_fetch = git
pkg_debbie_repo = https://github.com/crownedgrouse/debbie
pkg_debbie_commit = master
PACKAGES += decimal
pkg_decimal_name = decimal
pkg_decimal_description = An Erlang decimal arithmetic library
pkg_decimal_homepage = https://github.com/tim/erlang-decimal
pkg_decimal_fetch = git
pkg_decimal_repo = https://github.com/tim/erlang-decimal
pkg_decimal_commit = master
PACKAGES += detergent
pkg_detergent_name = detergent
pkg_detergent_description = An emulsifying Erlang SOAP library
pkg_detergent_homepage = https://github.com/devinus/detergent
pkg_detergent_fetch = git
pkg_detergent_repo = https://github.com/devinus/detergent
pkg_detergent_commit = master
PACKAGES += detest
pkg_detest_name = detest
pkg_detest_description = Tool for running tests on a cluster of erlang nodes
pkg_detest_homepage = https://github.com/biokoda/detest
pkg_detest_fetch = git
pkg_detest_repo = https://github.com/biokoda/detest
pkg_detest_commit = master
PACKAGES += dh_date
pkg_dh_date_name = dh_date
pkg_dh_date_description = Date formatting / parsing library for erlang
pkg_dh_date_homepage = https://github.com/daleharvey/dh_date
pkg_dh_date_fetch = git
pkg_dh_date_repo = https://github.com/daleharvey/dh_date
pkg_dh_date_commit = master
PACKAGES += dhtcrawler
pkg_dhtcrawler_name = dhtcrawler
pkg_dhtcrawler_description = dhtcrawler is a DHT crawler written in erlang. It can join a DHT network and crawl many P2P torrents.
pkg_dhtcrawler_homepage = https://github.com/kevinlynx/dhtcrawler
pkg_dhtcrawler_fetch = git
pkg_dhtcrawler_repo = https://github.com/kevinlynx/dhtcrawler
pkg_dhtcrawler_commit = master
PACKAGES += dirbusterl
pkg_dirbusterl_name = dirbusterl
pkg_dirbusterl_description = DirBuster successor in Erlang
pkg_dirbusterl_homepage = https://github.com/silentsignal/DirBustErl
pkg_dirbusterl_fetch = git
pkg_dirbusterl_repo = https://github.com/silentsignal/DirBustErl
pkg_dirbusterl_commit = master
PACKAGES += dispcount
pkg_dispcount_name = dispcount
pkg_dispcount_description = Erlang task dispatcher based on ETS counters.
pkg_dispcount_homepage = https://github.com/ferd/dispcount
pkg_dispcount_fetch = git
pkg_dispcount_repo = https://github.com/ferd/dispcount
pkg_dispcount_commit = master
PACKAGES += dlhttpc
pkg_dlhttpc_name = dlhttpc
pkg_dlhttpc_description = dispcount-based lhttpc fork for massive amounts of requests to limited endpoints
pkg_dlhttpc_homepage = https://github.com/ferd/dlhttpc
pkg_dlhttpc_fetch = git
pkg_dlhttpc_repo = https://github.com/ferd/dlhttpc
pkg_dlhttpc_commit = master
PACKAGES += dns
pkg_dns_name = dns
pkg_dns_description = Erlang DNS library
pkg_dns_homepage = https://github.com/aetrion/dns_erlang
pkg_dns_fetch = git
pkg_dns_repo = https://github.com/aetrion/dns_erlang
pkg_dns_commit = master
PACKAGES += dnssd
pkg_dnssd_name = dnssd
pkg_dnssd_description = Erlang interface to Apple's Bonjour D NS Service Discovery implementation
pkg_dnssd_homepage = https://github.com/benoitc/dnssd_erlang
pkg_dnssd_fetch = git
pkg_dnssd_repo = https://github.com/benoitc/dnssd_erlang
pkg_dnssd_commit = master
PACKAGES += dtl
pkg_dtl_name = dtl
pkg_dtl_description = Django Template Language: A full-featured port of the Django template engine to Erlang.
pkg_dtl_homepage = https://github.com/oinksoft/dtl
pkg_dtl_fetch = git
pkg_dtl_repo = https://github.com/oinksoft/dtl
pkg_dtl_commit = master
PACKAGES += dynamic_compile
pkg_dynamic_compile_name = dynamic_compile
pkg_dynamic_compile_description = compile and load erlang modules from string input
pkg_dynamic_compile_homepage = https://github.com/jkvor/dynamic_compile
pkg_dynamic_compile_fetch = git
pkg_dynamic_compile_repo = https://github.com/jkvor/dynamic_compile
pkg_dynamic_compile_commit = master
PACKAGES += e2
pkg_e2_name = e2
pkg_e2_description = Library to simply writing correct OTP applications.
pkg_e2_homepage = http://e2project.org
pkg_e2_fetch = git
pkg_e2_repo = https://github.com/gar1t/e2
pkg_e2_commit = master
PACKAGES += eamf
pkg_eamf_name = eamf
pkg_eamf_description = eAMF provides Action Message Format (AMF) support for Erlang
pkg_eamf_homepage = https://github.com/mrinalwadhwa/eamf
pkg_eamf_fetch = git
pkg_eamf_repo = https://github.com/mrinalwadhwa/eamf
pkg_eamf_commit = master
PACKAGES += eavro
pkg_eavro_name = eavro
pkg_eavro_description = Apache Avro encoder/decoder
pkg_eavro_homepage = https://github.com/SIfoxDevTeam/eavro
pkg_eavro_fetch = git
pkg_eavro_repo = https://github.com/SIfoxDevTeam/eavro
pkg_eavro_commit = master
PACKAGES += ecapnp
pkg_ecapnp_name = ecapnp
pkg_ecapnp_description = Cap'n Proto library for Erlang
pkg_ecapnp_homepage = https://github.com/kaos/ecapnp
pkg_ecapnp_fetch = git
pkg_ecapnp_repo = https://github.com/kaos/ecapnp
pkg_ecapnp_commit = master
PACKAGES += econfig
pkg_econfig_name = econfig
pkg_econfig_description = simple Erlang config handler using INI files
pkg_econfig_homepage = https://github.com/benoitc/econfig
pkg_econfig_fetch = git
pkg_econfig_repo = https://github.com/benoitc/econfig
pkg_econfig_commit = master
PACKAGES += edate
pkg_edate_name = edate
pkg_edate_description = date manipulation library for erlang
pkg_edate_homepage = https://github.com/dweldon/edate
pkg_edate_fetch = git
pkg_edate_repo = https://github.com/dweldon/edate
pkg_edate_commit = master
PACKAGES += edgar
pkg_edgar_name = edgar
pkg_edgar_description = Erlang Does GNU AR
pkg_edgar_homepage = https://github.com/crownedgrouse/edgar
pkg_edgar_fetch = git
pkg_edgar_repo = https://github.com/crownedgrouse/edgar
pkg_edgar_commit = master
PACKAGES += edis
pkg_edis_name = edis
pkg_edis_description = An Erlang implementation of Redis KV Store
pkg_edis_homepage = http://inaka.github.com/edis/
pkg_edis_fetch = git
pkg_edis_repo = https://github.com/inaka/edis
pkg_edis_commit = master
PACKAGES += edns
pkg_edns_name = edns
pkg_edns_description = Erlang/OTP DNS server
pkg_edns_homepage = https://github.com/hcvst/erlang-dns
pkg_edns_fetch = git
pkg_edns_repo = https://github.com/hcvst/erlang-dns
pkg_edns_commit = master
PACKAGES += edown
pkg_edown_name = edown
pkg_edown_description = EDoc extension for generating Github-flavored Markdown
pkg_edown_homepage = https://github.com/uwiger/edown
pkg_edown_fetch = git
pkg_edown_repo = https://github.com/uwiger/edown
pkg_edown_commit = master
PACKAGES += eep_app
pkg_eep_app_name = eep_app
pkg_eep_app_description = Embedded Event Processing
pkg_eep_app_homepage = https://github.com/darach/eep-erl
pkg_eep_app_fetch = git
pkg_eep_app_repo = https://github.com/darach/eep-erl
pkg_eep_app_commit = master
PACKAGES += eep
pkg_eep_name = eep
pkg_eep_description = Erlang Easy Profiling (eep) application provides a way to analyze application performance and call hierarchy
pkg_eep_homepage = https://github.com/virtan/eep
pkg_eep_fetch = git
pkg_eep_repo = https://github.com/virtan/eep
pkg_eep_commit = master
PACKAGES += efene
pkg_efene_name = efene
pkg_efene_description = Alternative syntax for the Erlang Programming Language focusing on simplicity, ease of use and programmer UX
pkg_efene_homepage = https://github.com/efene/efene
pkg_efene_fetch = git
pkg_efene_repo = https://github.com/efene/efene
pkg_efene_commit = master
PACKAGES += eganglia
pkg_eganglia_name = eganglia
pkg_eganglia_description = Erlang library to interact with Ganglia
pkg_eganglia_homepage = https://github.com/inaka/eganglia
pkg_eganglia_fetch = git
pkg_eganglia_repo = https://github.com/inaka/eganglia
pkg_eganglia_commit = v0.9.1
PACKAGES += egeoip
pkg_egeoip_name = egeoip
pkg_egeoip_description = Erlang IP Geolocation module, currently supporting the MaxMind GeoLite City Database.
pkg_egeoip_homepage = https://github.com/mochi/egeoip
pkg_egeoip_fetch = git
pkg_egeoip_repo = https://github.com/mochi/egeoip
pkg_egeoip_commit = master
PACKAGES += ehsa
pkg_ehsa_name = ehsa
pkg_ehsa_description = Erlang HTTP server basic and digest authentication modules
pkg_ehsa_homepage = https://bitbucket.org/a12n/ehsa
pkg_ehsa_fetch = hg
pkg_ehsa_repo = https://bitbucket.org/a12n/ehsa
pkg_ehsa_commit = 2.0.4
PACKAGES += ejabberd
pkg_ejabberd_name = ejabberd
pkg_ejabberd_description = Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform
pkg_ejabberd_homepage = https://github.com/processone/ejabberd
pkg_ejabberd_fetch = git
pkg_ejabberd_repo = https://github.com/processone/ejabberd
pkg_ejabberd_commit = master
PACKAGES += ej
pkg_ej_name = ej
pkg_ej_description = Helper module for working with Erlang terms representing JSON
pkg_ej_homepage = https://github.com/seth/ej
pkg_ej_fetch = git
pkg_ej_repo = https://github.com/seth/ej
pkg_ej_commit = master
PACKAGES += ejwt
pkg_ejwt_name = ejwt
pkg_ejwt_description = erlang library for JSON Web Token
pkg_ejwt_homepage = https://github.com/artefactop/ejwt
pkg_ejwt_fetch = git
pkg_ejwt_repo = https://github.com/artefactop/ejwt
pkg_ejwt_commit = master
PACKAGES += ekaf
pkg_ekaf_name = ekaf
pkg_ekaf_description = A minimal, high-performance Kafka client in Erlang.
pkg_ekaf_homepage = https://github.com/helpshift/ekaf
pkg_ekaf_fetch = git
pkg_ekaf_repo = https://github.com/helpshift/ekaf
pkg_ekaf_commit = master
PACKAGES += elarm
pkg_elarm_name = elarm
pkg_elarm_description = Alarm Manager for Erlang.
pkg_elarm_homepage = https://github.com/esl/elarm
pkg_elarm_fetch = git
pkg_elarm_repo = https://github.com/esl/elarm
pkg_elarm_commit = master
PACKAGES += eleveldb
pkg_eleveldb_name = eleveldb
pkg_eleveldb_description = Erlang LevelDB API
pkg_eleveldb_homepage = https://github.com/basho/eleveldb
pkg_eleveldb_fetch = git
pkg_eleveldb_repo = https://github.com/basho/eleveldb
pkg_eleveldb_commit = master
PACKAGES += elli
pkg_elli_name = elli
pkg_elli_description = Simple, robust and performant Erlang web server
pkg_elli_homepage = https://github.com/knutin/elli
pkg_elli_fetch = git
pkg_elli_repo = https://github.com/knutin/elli
pkg_elli_commit = master
PACKAGES += elvis
pkg_elvis_name = elvis
pkg_elvis_description = Erlang Style Reviewer
pkg_elvis_homepage = https://github.com/inaka/elvis
pkg_elvis_fetch = git
pkg_elvis_repo = https://github.com/inaka/elvis
pkg_elvis_commit = 0.2.4
PACKAGES += emagick
pkg_emagick_name = emagick
pkg_emagick_description = Wrapper for Graphics/ImageMagick command line tool.
pkg_emagick_homepage = https://github.com/kivra/emagick
pkg_emagick_fetch = git
pkg_emagick_repo = https://github.com/kivra/emagick
pkg_emagick_commit = master
PACKAGES += emysql
pkg_emysql_name = emysql
pkg_emysql_description = Stable, pure Erlang MySQL driver.
pkg_emysql_homepage = https://github.com/Eonblast/Emysql
pkg_emysql_fetch = git
pkg_emysql_repo = https://github.com/Eonblast/Emysql
pkg_emysql_commit = master
PACKAGES += enm
pkg_enm_name = enm
pkg_enm_description = Erlang driver for nanomsg
pkg_enm_homepage = https://github.com/basho/enm
pkg_enm_fetch = git
pkg_enm_repo = https://github.com/basho/enm
pkg_enm_commit = master
PACKAGES += entop
pkg_entop_name = entop
pkg_entop_description = A top-like tool for monitoring an Erlang node
pkg_entop_homepage = https://github.com/mazenharake/entop
pkg_entop_fetch = git
pkg_entop_repo = https://github.com/mazenharake/entop
pkg_entop_commit = master
PACKAGES += epcap
pkg_epcap_name = epcap
pkg_epcap_description = Erlang packet capture interface using pcap
pkg_epcap_homepage = https://github.com/msantos/epcap
pkg_epcap_fetch = git
pkg_epcap_repo = https://github.com/msantos/epcap
pkg_epcap_commit = master
PACKAGES += eper
pkg_eper_name = eper
pkg_eper_description = Erlang performance and debugging tools.
pkg_eper_homepage = https://github.com/massemanet/eper
pkg_eper_fetch = git
pkg_eper_repo = https://github.com/massemanet/eper
pkg_eper_commit = master
PACKAGES += epgsql
pkg_epgsql_name = epgsql
pkg_epgsql_description = Erlang PostgreSQL client library.
pkg_epgsql_homepage = https://github.com/epgsql/epgsql
pkg_epgsql_fetch = git
pkg_epgsql_repo = https://github.com/epgsql/epgsql
pkg_epgsql_commit = master
PACKAGES += episcina
pkg_episcina_name = episcina
pkg_episcina_description = A simple non intrusive resource pool for connections
pkg_episcina_homepage = https://github.com/erlware/episcina
pkg_episcina_fetch = git
pkg_episcina_repo = https://github.com/erlware/episcina
pkg_episcina_commit = master
PACKAGES += eplot
pkg_eplot_name = eplot
pkg_eplot_description = A plot engine written in erlang.
pkg_eplot_homepage = https://github.com/psyeugenic/eplot
pkg_eplot_fetch = git
pkg_eplot_repo = https://github.com/psyeugenic/eplot
pkg_eplot_commit = master
PACKAGES += epocxy
pkg_epocxy_name = epocxy
pkg_epocxy_description = Erlang Patterns of Concurrency
pkg_epocxy_homepage = https://github.com/duomark/epocxy
pkg_epocxy_fetch = git
pkg_epocxy_repo = https://github.com/duomark/epocxy
pkg_epocxy_commit = master
PACKAGES += epubnub
pkg_epubnub_name = epubnub
pkg_epubnub_description = Erlang PubNub API
pkg_epubnub_homepage = https://github.com/tsloughter/epubnub
pkg_epubnub_fetch = git
pkg_epubnub_repo = https://github.com/tsloughter/epubnub
pkg_epubnub_commit = master
PACKAGES += eqm
pkg_eqm_name = eqm
pkg_eqm_description = Erlang pub sub with supply-demand channels
pkg_eqm_homepage = https://github.com/loucash/eqm
pkg_eqm_fetch = git
pkg_eqm_repo = https://github.com/loucash/eqm
pkg_eqm_commit = master
PACKAGES += eredis
pkg_eredis_name = eredis
pkg_eredis_description = Erlang Redis client
pkg_eredis_homepage = https://github.com/wooga/eredis
pkg_eredis_fetch = git
pkg_eredis_repo = https://github.com/wooga/eredis
pkg_eredis_commit = master
PACKAGES += eredis_pool
pkg_eredis_pool_name = eredis_pool
pkg_eredis_pool_description = eredis_pool is Pool of Redis clients, using eredis and poolboy.
pkg_eredis_pool_homepage = https://github.com/hiroeorz/eredis_pool
pkg_eredis_pool_fetch = git
pkg_eredis_pool_repo = https://github.com/hiroeorz/eredis_pool
pkg_eredis_pool_commit = master
PACKAGES += erlang_cep
pkg_erlang_cep_name = erlang_cep
pkg_erlang_cep_description = A basic CEP package written in erlang
pkg_erlang_cep_homepage = https://github.com/danmacklin/erlang_cep
pkg_erlang_cep_fetch = git
pkg_erlang_cep_repo = https://github.com/danmacklin/erlang_cep
pkg_erlang_cep_commit = master
PACKAGES += erlang_js
pkg_erlang_js_name = erlang_js
pkg_erlang_js_description = A linked-in driver for Erlang to Mozilla's Spidermonkey Javascript runtime.
pkg_erlang_js_homepage = https://github.com/basho/erlang_js
pkg_erlang_js_fetch = git
pkg_erlang_js_repo = https://github.com/basho/erlang_js
pkg_erlang_js_commit = master
PACKAGES += erlang_localtime
pkg_erlang_localtime_name = erlang_localtime
pkg_erlang_localtime_description = Erlang library for conversion from one local time to another
pkg_erlang_localtime_homepage = https://github.com/dmitryme/erlang_localtime
pkg_erlang_localtime_fetch = git
pkg_erlang_localtime_repo = https://github.com/dmitryme/erlang_localtime
pkg_erlang_localtime_commit = master
PACKAGES += erlang_smtp
pkg_erlang_smtp_name = erlang_smtp
pkg_erlang_smtp_description = Erlang SMTP and POP3 server code.
pkg_erlang_smtp_homepage = https://github.com/tonyg/erlang-smtp
pkg_erlang_smtp_fetch = git
pkg_erlang_smtp_repo = https://github.com/tonyg/erlang-smtp
pkg_erlang_smtp_commit = master
PACKAGES += erlang_term
pkg_erlang_term_name = erlang_term
pkg_erlang_term_description = Erlang Term Info
pkg_erlang_term_homepage = https://github.com/okeuday/erlang_term
pkg_erlang_term_fetch = git
pkg_erlang_term_repo = https://github.com/okeuday/erlang_term
pkg_erlang_term_commit = master
PACKAGES += erlastic_search
pkg_erlastic_search_name = erlastic_search
pkg_erlastic_search_description = An Erlang app for communicating with Elastic Search's rest interface.
pkg_erlastic_search_homepage = https://github.com/tsloughter/erlastic_search
pkg_erlastic_search_fetch = git
pkg_erlastic_search_repo = https://github.com/tsloughter/erlastic_search
pkg_erlastic_search_commit = master
PACKAGES += erlasticsearch
pkg_erlasticsearch_name = erlasticsearch
pkg_erlasticsearch_description = Erlang thrift interface to elastic_search
pkg_erlasticsearch_homepage = https://github.com/dieswaytoofast/erlasticsearch
pkg_erlasticsearch_fetch = git
pkg_erlasticsearch_repo = https://github.com/dieswaytoofast/erlasticsearch
pkg_erlasticsearch_commit = master
PACKAGES += erlbrake
pkg_erlbrake_name = erlbrake
pkg_erlbrake_description = Erlang Airbrake notification client
pkg_erlbrake_homepage = https://github.com/kenpratt/erlbrake
pkg_erlbrake_fetch = git
pkg_erlbrake_repo = https://github.com/kenpratt/erlbrake
pkg_erlbrake_commit = master
PACKAGES += erlcloud
pkg_erlcloud_name = erlcloud
pkg_erlcloud_description = Cloud Computing library for erlang (Amazon EC2, S3, SQS, SimpleDB, Mechanical Turk, ELB)
pkg_erlcloud_homepage = https://github.com/gleber/erlcloud
pkg_erlcloud_fetch = git
pkg_erlcloud_repo = https://github.com/gleber/erlcloud
pkg_erlcloud_commit = master
PACKAGES += erlcron
pkg_erlcron_name = erlcron
pkg_erlcron_description = Erlang cronish system
pkg_erlcron_homepage = https://github.com/erlware/erlcron
pkg_erlcron_fetch = git
pkg_erlcron_repo = https://github.com/erlware/erlcron
pkg_erlcron_commit = master
PACKAGES += erldb
pkg_erldb_name = erldb
pkg_erldb_description = ORM (Object-relational mapping) application implemented in Erlang
pkg_erldb_homepage = http://erldb.org
pkg_erldb_fetch = git
pkg_erldb_repo = https://github.com/erldb/erldb
pkg_erldb_commit = master
PACKAGES += erldis
pkg_erldis_name = erldis
pkg_erldis_description = redis erlang client library
pkg_erldis_homepage = https://github.com/cstar/erldis
pkg_erldis_fetch = git
pkg_erldis_repo = https://github.com/cstar/erldis
pkg_erldis_commit = master
PACKAGES += erldns
pkg_erldns_name = erldns
pkg_erldns_description = DNS server, in erlang.
pkg_erldns_homepage = https://github.com/aetrion/erl-dns
pkg_erldns_fetch = git
pkg_erldns_repo = https://github.com/aetrion/erl-dns
pkg_erldns_commit = master
PACKAGES += erldocker
pkg_erldocker_name = erldocker
pkg_erldocker_description = Docker Remote API client for Erlang
pkg_erldocker_homepage = https://github.com/proger/erldocker
pkg_erldocker_fetch = git
pkg_erldocker_repo = https://github.com/proger/erldocker
pkg_erldocker_commit = master
PACKAGES += erlfsmon
pkg_erlfsmon_name = erlfsmon
pkg_erlfsmon_description = Erlang filesystem event watcher for Linux and OSX
pkg_erlfsmon_homepage = https://github.com/proger/erlfsmon
pkg_erlfsmon_fetch = git
pkg_erlfsmon_repo = https://github.com/proger/erlfsmon
pkg_erlfsmon_commit = master
PACKAGES += erlgit
pkg_erlgit_name = erlgit
pkg_erlgit_description = Erlang convenience wrapper around git executable
pkg_erlgit_homepage = https://github.com/gleber/erlgit
pkg_erlgit_fetch = git
pkg_erlgit_repo = https://github.com/gleber/erlgit
pkg_erlgit_commit = master
PACKAGES += erlguten
pkg_erlguten_name = erlguten
pkg_erlguten_description = ErlGuten is a system for high-quality typesetting, written purely in Erlang.
pkg_erlguten_homepage = https://github.com/richcarl/erlguten
pkg_erlguten_fetch = git
pkg_erlguten_repo = https://github.com/richcarl/erlguten
pkg_erlguten_commit = master
PACKAGES += erlmc
pkg_erlmc_name = erlmc
pkg_erlmc_description = Erlang memcached binary protocol client
pkg_erlmc_homepage = https://github.com/jkvor/erlmc
pkg_erlmc_fetch = git
pkg_erlmc_repo = https://github.com/jkvor/erlmc
pkg_erlmc_commit = master
PACKAGES += erlmongo
pkg_erlmongo_name = erlmongo
pkg_erlmongo_description = Record based Erlang driver for MongoDB with gridfs support
pkg_erlmongo_homepage = https://github.com/SergejJurecko/erlmongo
pkg_erlmongo_fetch = git
pkg_erlmongo_repo = https://github.com/SergejJurecko/erlmongo
pkg_erlmongo_commit = master
PACKAGES += erlog
pkg_erlog_name = erlog
pkg_erlog_description = Prolog interpreter in and for Erlang
pkg_erlog_homepage = https://github.com/rvirding/erlog
pkg_erlog_fetch = git
pkg_erlog_repo = https://github.com/rvirding/erlog
pkg_erlog_commit = master
PACKAGES += erlpass
pkg_erlpass_name = erlpass
pkg_erlpass_description = A library to handle password hashing and changing in a safe manner, independent from any kind of storage whatsoever.
pkg_erlpass_homepage = https://github.com/ferd/erlpass
pkg_erlpass_fetch = git
pkg_erlpass_repo = https://github.com/ferd/erlpass
pkg_erlpass_commit = master
PACKAGES += erlport
pkg_erlport_name = erlport
pkg_erlport_description = ErlPort - connect Erlang to other languages
pkg_erlport_homepage = https://github.com/hdima/erlport
pkg_erlport_fetch = git
pkg_erlport_repo = https://github.com/hdima/erlport
pkg_erlport_commit = master
PACKAGES += erlsha2
pkg_erlsha2_name = erlsha2
pkg_erlsha2_description = SHA-224, SHA-256, SHA-384, SHA-512 implemented in Erlang NIFs.
pkg_erlsha2_homepage = https://github.com/vinoski/erlsha2
pkg_erlsha2_fetch = git
pkg_erlsha2_repo = https://github.com/vinoski/erlsha2
pkg_erlsha2_commit = master
PACKAGES += erlsh
pkg_erlsh_name = erlsh
pkg_erlsh_description = Erlang shell tools
pkg_erlsh_homepage = https://github.com/proger/erlsh
pkg_erlsh_fetch = git
pkg_erlsh_repo = https://github.com/proger/erlsh
pkg_erlsh_commit = master
PACKAGES += erlsom
pkg_erlsom_name = erlsom
pkg_erlsom_description = XML parser for Erlang
pkg_erlsom_homepage = https://github.com/willemdj/erlsom
pkg_erlsom_fetch = git
pkg_erlsom_repo = https://github.com/willemdj/erlsom
pkg_erlsom_commit = master
PACKAGES += erl_streams
pkg_erl_streams_name = erl_streams
pkg_erl_streams_description = Streams in Erlang
pkg_erl_streams_homepage = https://github.com/epappas/erl_streams
pkg_erl_streams_fetch = git
pkg_erl_streams_repo = https://github.com/epappas/erl_streams
pkg_erl_streams_commit = master
PACKAGES += erlubi
pkg_erlubi_name = erlubi
pkg_erlubi_description = Ubigraph Erlang Client (and Process Visualizer)
pkg_erlubi_homepage = https://github.com/krestenkrab/erlubi
pkg_erlubi_fetch = git
pkg_erlubi_repo = https://github.com/krestenkrab/erlubi
pkg_erlubi_commit = master
PACKAGES += erlvolt
pkg_erlvolt_name = erlvolt
pkg_erlvolt_description = VoltDB Erlang Client Driver
pkg_erlvolt_homepage = https://github.com/VoltDB/voltdb-client-erlang
pkg_erlvolt_fetch = git
pkg_erlvolt_repo = https://github.com/VoltDB/voltdb-client-erlang
pkg_erlvolt_commit = master
PACKAGES += erlware_commons
pkg_erlware_commons_name = erlware_commons
pkg_erlware_commons_description = Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components.
pkg_erlware_commons_homepage = https://github.com/erlware/erlware_commons
pkg_erlware_commons_fetch = git
pkg_erlware_commons_repo = https://github.com/erlware/erlware_commons
pkg_erlware_commons_commit = master
PACKAGES += erlydtl
pkg_erlydtl_name = erlydtl
pkg_erlydtl_description = Django Template Language for Erlang.
pkg_erlydtl_homepage = https://github.com/erlydtl/erlydtl
pkg_erlydtl_fetch = git
pkg_erlydtl_repo = https://github.com/erlydtl/erlydtl
pkg_erlydtl_commit = master
PACKAGES += errd
pkg_errd_name = errd
pkg_errd_description = Erlang RRDTool library
pkg_errd_homepage = https://github.com/archaelus/errd
pkg_errd_fetch = git
pkg_errd_repo = https://github.com/archaelus/errd
pkg_errd_commit = master
PACKAGES += erserve
pkg_erserve_name = erserve
pkg_erserve_description = Erlang/Rserve communication interface
pkg_erserve_homepage = https://github.com/del/erserve
pkg_erserve_fetch = git
pkg_erserve_repo = https://github.com/del/erserve
pkg_erserve_commit = master
PACKAGES += erwa
pkg_erwa_name = erwa
pkg_erwa_description = A WAMP router and client written in Erlang.
pkg_erwa_homepage = https://github.com/bwegh/erwa
pkg_erwa_fetch = git
pkg_erwa_repo = https://github.com/bwegh/erwa
pkg_erwa_commit = 0.1.1
PACKAGES += espec
pkg_espec_name = espec
pkg_espec_description = ESpec: Behaviour driven development framework for Erlang
pkg_espec_homepage = https://github.com/lucaspiller/espec
pkg_espec_fetch = git
pkg_espec_repo = https://github.com/lucaspiller/espec
pkg_espec_commit = master
PACKAGES += estatsd
pkg_estatsd_name = estatsd
pkg_estatsd_description = Erlang stats aggregation app that periodically flushes data to graphite
pkg_estatsd_homepage = https://github.com/RJ/estatsd
pkg_estatsd_fetch = git
pkg_estatsd_repo = https://github.com/RJ/estatsd
pkg_estatsd_commit = master
PACKAGES += etap
pkg_etap_name = etap
pkg_etap_description = etap is a simple erlang testing library that provides TAP compliant output.
pkg_etap_homepage = https://github.com/ngerakines/etap
pkg_etap_fetch = git
pkg_etap_repo = https://github.com/ngerakines/etap
pkg_etap_commit = master
PACKAGES += etest_http
pkg_etest_http_name = etest_http
pkg_etest_http_description = etest Assertions around HTTP (client-side)
pkg_etest_http_homepage = https://github.com/wooga/etest_http
pkg_etest_http_fetch = git
pkg_etest_http_repo = https://github.com/wooga/etest_http
pkg_etest_http_commit = master
PACKAGES += etest
pkg_etest_name = etest
pkg_etest_description = A lightweight, convention over configuration test framework for Erlang
pkg_etest_homepage = https://github.com/wooga/etest
pkg_etest_fetch = git
pkg_etest_repo = https://github.com/wooga/etest
pkg_etest_commit = master
PACKAGES += etoml
pkg_etoml_name = etoml
pkg_etoml_description = TOML language erlang parser
pkg_etoml_homepage = https://github.com/kalta/etoml
pkg_etoml_fetch = git
pkg_etoml_repo = https://github.com/kalta/etoml
pkg_etoml_commit = master
PACKAGES += eunit_formatters
pkg_eunit_formatters_name = eunit_formatters
pkg_eunit_formatters_description = Because eunit's output sucks. Let's make it better.
pkg_eunit_formatters_homepage = https://github.com/seancribbs/eunit_formatters
pkg_eunit_formatters_fetch = git
pkg_eunit_formatters_repo = https://github.com/seancribbs/eunit_formatters
pkg_eunit_formatters_commit = master
PACKAGES += eunit
pkg_eunit_name = eunit
pkg_eunit_description = The EUnit lightweight unit testing framework for Erlang - this is the canonical development repository.
pkg_eunit_homepage = https://github.com/richcarl/eunit
pkg_eunit_fetch = git
pkg_eunit_repo = https://github.com/richcarl/eunit
pkg_eunit_commit = master
PACKAGES += euthanasia
pkg_euthanasia_name = euthanasia
pkg_euthanasia_description = Merciful killer for your Erlang processes
pkg_euthanasia_homepage = https://github.com/doubleyou/euthanasia
pkg_euthanasia_fetch = git
pkg_euthanasia_repo = https://github.com/doubleyou/euthanasia
pkg_euthanasia_commit = master
PACKAGES += evum
pkg_evum_name = evum
pkg_evum_description = Spawn Linux VMs as Erlang processes in the Erlang VM
pkg_evum_homepage = https://github.com/msantos/evum
pkg_evum_fetch = git
pkg_evum_repo = https://github.com/msantos/evum
pkg_evum_commit = master
PACKAGES += exec
pkg_exec_name = exec
pkg_exec_description = Execute and control OS processes from Erlang/OTP.
pkg_exec_homepage = http://saleyn.github.com/erlexec
pkg_exec_fetch = git
pkg_exec_repo = https://github.com/saleyn/erlexec
pkg_exec_commit = master
PACKAGES += exml
pkg_exml_name = exml
pkg_exml_description = XML parsing library in Erlang
pkg_exml_homepage = https://github.com/paulgray/exml
pkg_exml_fetch = git
pkg_exml_repo = https://github.com/paulgray/exml
pkg_exml_commit = master
PACKAGES += exometer
pkg_exometer_name = exometer
pkg_exometer_description = Basic measurement objects and probe behavior
pkg_exometer_homepage = https://github.com/Feuerlabs/exometer
pkg_exometer_fetch = git
pkg_exometer_repo = https://github.com/Feuerlabs/exometer
pkg_exometer_commit = 1.2
PACKAGES += exs1024
pkg_exs1024_name = exs1024
pkg_exs1024_description = Xorshift1024star pseudo random number generator for Erlang.
pkg_exs1024_homepage = https://github.com/jj1bdx/exs1024
pkg_exs1024_fetch = git
pkg_exs1024_repo = https://github.com/jj1bdx/exs1024
pkg_exs1024_commit = master
PACKAGES += exs64
pkg_exs64_name = exs64
pkg_exs64_description = Xorshift64star pseudo random number generator for Erlang.
pkg_exs64_homepage = https://github.com/jj1bdx/exs64
pkg_exs64_fetch = git
pkg_exs64_repo = https://github.com/jj1bdx/exs64
pkg_exs64_commit = master
PACKAGES += exsplus116
pkg_exsplus116_name = exsplus116
pkg_exsplus116_description = Xorshift116plus for Erlang
pkg_exsplus116_homepage = https://github.com/jj1bdx/exsplus116
pkg_exsplus116_fetch = git
pkg_exsplus116_repo = https://github.com/jj1bdx/exsplus116
pkg_exsplus116_commit = master
PACKAGES += exsplus128
pkg_exsplus128_name = exsplus128
pkg_exsplus128_description = Xorshift128plus pseudo random number generator for Erlang.
pkg_exsplus128_homepage = https://github.com/jj1bdx/exsplus128
pkg_exsplus128_fetch = git
pkg_exsplus128_repo = https://github.com/jj1bdx/exsplus128
pkg_exsplus128_commit = master
PACKAGES += ezmq
pkg_ezmq_name = ezmq
pkg_ezmq_description = zMQ implemented in Erlang
pkg_ezmq_homepage = https://github.com/RoadRunnr/ezmq
pkg_ezmq_fetch = git
pkg_ezmq_repo = https://github.com/RoadRunnr/ezmq
pkg_ezmq_commit = master
PACKAGES += ezmtp
pkg_ezmtp_name = ezmtp
pkg_ezmtp_description = ZMTP protocol in pure Erlang.
pkg_ezmtp_homepage = https://github.com/a13x/ezmtp
pkg_ezmtp_fetch = git
pkg_ezmtp_repo = https://github.com/a13x/ezmtp
pkg_ezmtp_commit = master
PACKAGES += fast_disk_log
pkg_fast_disk_log_name = fast_disk_log
pkg_fast_disk_log_description = Pool-based asynchronous Erlang disk logger
pkg_fast_disk_log_homepage = https://github.com/lpgauth/fast_disk_log
pkg_fast_disk_log_fetch = git
pkg_fast_disk_log_repo = https://github.com/lpgauth/fast_disk_log
pkg_fast_disk_log_commit = master
PACKAGES += feeder
pkg_feeder_name = feeder
pkg_feeder_description = Stream parse RSS and Atom formatted XML feeds.
pkg_feeder_homepage = https://github.com/michaelnisi/feeder
pkg_feeder_fetch = git
pkg_feeder_repo = https://github.com/michaelnisi/feeder
pkg_feeder_commit = v1.4.6
PACKAGES += fix
pkg_fix_name = fix
pkg_fix_description = http://fixprotocol.org/ implementation.
pkg_fix_homepage = https://github.com/maxlapshin/fix
pkg_fix_fetch = git
pkg_fix_repo = https://github.com/maxlapshin/fix
pkg_fix_commit = master
PACKAGES += flower
pkg_flower_name = flower
pkg_flower_description = FlowER - a Erlang OpenFlow development platform
pkg_flower_homepage = https://github.com/travelping/flower
pkg_flower_fetch = git
pkg_flower_repo = https://github.com/travelping/flower
pkg_flower_commit = master
PACKAGES += fn
pkg_fn_name = fn
pkg_fn_description = Function utilities for Erlang
pkg_fn_homepage = https://github.com/reiddraper/fn
pkg_fn_fetch = git
pkg_fn_repo = https://github.com/reiddraper/fn
pkg_fn_commit = master
PACKAGES += folsom_cowboy
pkg_folsom_cowboy_name = folsom_cowboy
pkg_folsom_cowboy_description = A Cowboy based Folsom HTTP Wrapper.
pkg_folsom_cowboy_homepage = https://github.com/boundary/folsom_cowboy
pkg_folsom_cowboy_fetch = git
pkg_folsom_cowboy_repo = https://github.com/boundary/folsom_cowboy
pkg_folsom_cowboy_commit = master
PACKAGES += folsomite
pkg_folsomite_name = folsomite
pkg_folsomite_description = blow up your graphite / riemann server with folsom metrics
pkg_folsomite_homepage = https://github.com/campanja/folsomite
pkg_folsomite_fetch = git
pkg_folsomite_repo = https://github.com/campanja/folsomite
pkg_folsomite_commit = master
PACKAGES += folsom
pkg_folsom_name = folsom
pkg_folsom_description = Expose Erlang Events and Metrics
pkg_folsom_homepage = https://github.com/boundary/folsom
pkg_folsom_fetch = git
pkg_folsom_repo = https://github.com/boundary/folsom
pkg_folsom_commit = master
PACKAGES += fs
pkg_fs_name = fs
pkg_fs_description = Erlang FileSystem Listener
pkg_fs_homepage = https://github.com/synrc/fs
pkg_fs_fetch = git
pkg_fs_repo = https://github.com/synrc/fs
pkg_fs_commit = master
PACKAGES += fuse
pkg_fuse_name = fuse
pkg_fuse_description = A Circuit Breaker for Erlang
pkg_fuse_homepage = https://github.com/jlouis/fuse
pkg_fuse_fetch = git
pkg_fuse_repo = https://github.com/jlouis/fuse
pkg_fuse_commit = master
PACKAGES += gcm
pkg_gcm_name = gcm
pkg_gcm_description = An Erlang application for Google Cloud Messaging
pkg_gcm_homepage = https://github.com/pdincau/gcm-erlang
pkg_gcm_fetch = git
pkg_gcm_repo = https://github.com/pdincau/gcm-erlang
pkg_gcm_commit = master
PACKAGES += gcprof
pkg_gcprof_name = gcprof
pkg_gcprof_description = Garbage Collection profiler for Erlang
pkg_gcprof_homepage = https://github.com/knutin/gcprof
pkg_gcprof_fetch = git
pkg_gcprof_repo = https://github.com/knutin/gcprof
pkg_gcprof_commit = master
PACKAGES += geas
pkg_geas_name = geas
pkg_geas_description = Guess Erlang Application Scattering
pkg_geas_homepage = https://github.com/crownedgrouse/geas
pkg_geas_fetch = git
pkg_geas_repo = https://github.com/crownedgrouse/geas
pkg_geas_commit = master
PACKAGES += geef
pkg_geef_name = geef
pkg_geef_description = Git NEEEEF (Erlang NIF)
pkg_geef_homepage = https://github.com/carlosmn/geef
pkg_geef_fetch = git
pkg_geef_repo = https://github.com/carlosmn/geef
pkg_geef_commit = master
PACKAGES += gen_cycle
pkg_gen_cycle_name = gen_cycle
pkg_gen_cycle_description = Simple, generic OTP behaviour for recurring tasks
pkg_gen_cycle_homepage = https://github.com/aerosol/gen_cycle
pkg_gen_cycle_fetch = git
pkg_gen_cycle_repo = https://github.com/aerosol/gen_cycle
pkg_gen_cycle_commit = develop
PACKAGES += gen_icmp
pkg_gen_icmp_name = gen_icmp
pkg_gen_icmp_description = Erlang interface to ICMP sockets
pkg_gen_icmp_homepage = https://github.com/msantos/gen_icmp
pkg_gen_icmp_fetch = git
pkg_gen_icmp_repo = https://github.com/msantos/gen_icmp
pkg_gen_icmp_commit = master
PACKAGES += gen_nb_server
pkg_gen_nb_server_name = gen_nb_server
pkg_gen_nb_server_description = OTP behavior for writing non-blocking servers
pkg_gen_nb_server_homepage = https://github.com/kevsmith/gen_nb_server
pkg_gen_nb_server_fetch = git
pkg_gen_nb_server_repo = https://github.com/kevsmith/gen_nb_server
pkg_gen_nb_server_commit = master
PACKAGES += gen_paxos
pkg_gen_paxos_name = gen_paxos
pkg_gen_paxos_description = An Erlang/OTP-style implementation of the PAXOS distributed consensus protocol
pkg_gen_paxos_homepage = https://github.com/gburd/gen_paxos
pkg_gen_paxos_fetch = git
pkg_gen_paxos_repo = https://github.com/gburd/gen_paxos
pkg_gen_paxos_commit = master
PACKAGES += gen_smtp
pkg_gen_smtp_name = gen_smtp
pkg_gen_smtp_description = A generic Erlang SMTP server and client that can be extended via callback modules
pkg_gen_smtp_homepage = https://github.com/Vagabond/gen_smtp
pkg_gen_smtp_fetch = git
pkg_gen_smtp_repo = https://github.com/Vagabond/gen_smtp
pkg_gen_smtp_commit = master
PACKAGES += gen_tracker
pkg_gen_tracker_name = gen_tracker
pkg_gen_tracker_description = supervisor with ets handling of children and their metadata
pkg_gen_tracker_homepage = https://github.com/erlyvideo/gen_tracker
pkg_gen_tracker_fetch = git
pkg_gen_tracker_repo = https://github.com/erlyvideo/gen_tracker
pkg_gen_tracker_commit = master
PACKAGES += gen_unix
pkg_gen_unix_name = gen_unix
pkg_gen_unix_description = Erlang Unix socket interface
pkg_gen_unix_homepage = https://github.com/msantos/gen_unix
pkg_gen_unix_fetch = git
pkg_gen_unix_repo = https://github.com/msantos/gen_unix
pkg_gen_unix_commit = master
PACKAGES += getopt
pkg_getopt_name = getopt
pkg_getopt_description = Module to parse command line arguments using the GNU getopt syntax
pkg_getopt_homepage = https://github.com/jcomellas/getopt
pkg_getopt_fetch = git
pkg_getopt_repo = https://github.com/jcomellas/getopt
pkg_getopt_commit = master
PACKAGES += gettext
pkg_gettext_name = gettext
pkg_gettext_description = Erlang internationalization library.
pkg_gettext_homepage = https://github.com/etnt/gettext
pkg_gettext_fetch = git
pkg_gettext_repo = https://github.com/etnt/gettext
pkg_gettext_commit = master
PACKAGES += giallo
pkg_giallo_name = giallo
pkg_giallo_description = Small and flexible web framework on top of Cowboy
pkg_giallo_homepage = https://github.com/kivra/giallo
pkg_giallo_fetch = git
pkg_giallo_repo = https://github.com/kivra/giallo
pkg_giallo_commit = master
PACKAGES += gin
pkg_gin_name = gin
pkg_gin_description = The guards and for Erlang parse_transform
pkg_gin_homepage = https://github.com/mad-cocktail/gin
pkg_gin_fetch = git
pkg_gin_repo = https://github.com/mad-cocktail/gin
pkg_gin_commit = master
PACKAGES += gitty
pkg_gitty_name = gitty
pkg_gitty_description = Git access in erlang
pkg_gitty_homepage = https://github.com/maxlapshin/gitty
pkg_gitty_fetch = git
pkg_gitty_repo = https://github.com/maxlapshin/gitty
pkg_gitty_commit = master
PACKAGES += gold_fever
pkg_gold_fever_name = gold_fever
pkg_gold_fever_description = A Treasure Hunt for Erlangers
pkg_gold_fever_homepage = https://github.com/inaka/gold_fever
pkg_gold_fever_fetch = git
pkg_gold_fever_repo = https://github.com/inaka/gold_fever
pkg_gold_fever_commit = master
PACKAGES += gossiperl
pkg_gossiperl_name = gossiperl
pkg_gossiperl_description = Gossip middleware in Erlang
pkg_gossiperl_homepage = http://gossiperl.com/
pkg_gossiperl_fetch = git
pkg_gossiperl_repo = https://github.com/gossiperl/gossiperl
pkg_gossiperl_commit = master
PACKAGES += gpb
pkg_gpb_name = gpb
pkg_gpb_description = A Google Protobuf implementation for Erlang
pkg_gpb_homepage = https://github.com/tomas-abrahamsson/gpb
pkg_gpb_fetch = git
pkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb
pkg_gpb_commit = master
PACKAGES += gproc
pkg_gproc_name = gproc
pkg_gproc_description = Extended process registry for Erlang
pkg_gproc_homepage = https://github.com/uwiger/gproc
pkg_gproc_fetch = git
pkg_gproc_repo = https://github.com/uwiger/gproc
pkg_gproc_commit = master
PACKAGES += grapherl
pkg_grapherl_name = grapherl
pkg_grapherl_description = Create graphs of Erlang systems and programs
pkg_grapherl_homepage = https://github.com/eproxus/grapherl
pkg_grapherl_fetch = git
pkg_grapherl_repo = https://github.com/eproxus/grapherl
pkg_grapherl_commit = master
PACKAGES += gun
pkg_gun_name = gun
pkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang.
pkg_gun_homepage = http//ninenines.eu
pkg_gun_fetch = git
pkg_gun_repo = https://github.com/ninenines/gun
pkg_gun_commit = master
PACKAGES += gut
pkg_gut_name = gut
pkg_gut_description = gut is a template printing, aka scaffolding, tool for Erlang. Like rails generate or yeoman
pkg_gut_homepage = https://github.com/unbalancedparentheses/gut
pkg_gut_fetch = git
pkg_gut_repo = https://github.com/unbalancedparentheses/gut
pkg_gut_commit = master
PACKAGES += hackney
pkg_hackney_name = hackney
pkg_hackney_description = simple HTTP client in Erlang
pkg_hackney_homepage = https://github.com/benoitc/hackney
pkg_hackney_fetch = git
pkg_hackney_repo = https://github.com/benoitc/hackney
pkg_hackney_commit = master
PACKAGES += hamcrest
pkg_hamcrest_name = hamcrest
pkg_hamcrest_description = Erlang port of Hamcrest
pkg_hamcrest_homepage = https://github.com/hyperthunk/hamcrest-erlang
pkg_hamcrest_fetch = git
pkg_hamcrest_repo = https://github.com/hyperthunk/hamcrest-erlang
pkg_hamcrest_commit = master
PACKAGES += hanoidb
pkg_hanoidb_name = hanoidb
pkg_hanoidb_description = Erlang LSM BTree Storage
pkg_hanoidb_homepage = https://github.com/krestenkrab/hanoidb
pkg_hanoidb_fetch = git
pkg_hanoidb_repo = https://github.com/krestenkrab/hanoidb
pkg_hanoidb_commit = master
PACKAGES += hottub
pkg_hottub_name = hottub
pkg_hottub_description = Permanent Erlang Worker Pool
pkg_hottub_homepage = https://github.com/bfrog/hottub
pkg_hottub_fetch = git
pkg_hottub_repo = https://github.com/bfrog/hottub
pkg_hottub_commit = master
PACKAGES += hpack
pkg_hpack_name = hpack
pkg_hpack_description = HPACK Implementation for Erlang
pkg_hpack_homepage = https://github.com/joedevivo/hpack
pkg_hpack_fetch = git
pkg_hpack_repo = https://github.com/joedevivo/hpack
pkg_hpack_commit = master
PACKAGES += hyper
pkg_hyper_name = hyper
pkg_hyper_description = Erlang implementation of HyperLogLog
pkg_hyper_homepage = https://github.com/GameAnalytics/hyper
pkg_hyper_fetch = git
pkg_hyper_repo = https://github.com/GameAnalytics/hyper
pkg_hyper_commit = master
PACKAGES += ibrowse
pkg_ibrowse_name = ibrowse
pkg_ibrowse_description = Erlang HTTP client
pkg_ibrowse_homepage = https://github.com/cmullaparthi/ibrowse
pkg_ibrowse_fetch = git
pkg_ibrowse_repo = https://github.com/cmullaparthi/ibrowse
pkg_ibrowse_commit = v4.1.1
PACKAGES += ierlang
pkg_ierlang_name = ierlang
pkg_ierlang_description = An Erlang language kernel for IPython.
pkg_ierlang_homepage = https://github.com/robbielynch/ierlang
pkg_ierlang_fetch = git
pkg_ierlang_repo = https://github.com/robbielynch/ierlang
pkg_ierlang_commit = master
PACKAGES += iota
pkg_iota_name = iota
pkg_iota_description = iota (Inter-dependency Objective Testing Apparatus) - a tool to enforce clean separation of responsibilities in Erlang code
pkg_iota_homepage = https://github.com/jpgneves/iota
pkg_iota_fetch = git
pkg_iota_repo = https://github.com/jpgneves/iota
pkg_iota_commit = master
PACKAGES += ircd
pkg_ircd_name = ircd
pkg_ircd_description = A pluggable IRC daemon application/library for Erlang.
pkg_ircd_homepage = https://github.com/tonyg/erlang-ircd
pkg_ircd_fetch = git
pkg_ircd_repo = https://github.com/tonyg/erlang-ircd
pkg_ircd_commit = master
PACKAGES += irc_lib
pkg_irc_lib_name = irc_lib
pkg_irc_lib_description = Erlang irc client library
pkg_irc_lib_homepage = https://github.com/OtpChatBot/irc_lib
pkg_irc_lib_fetch = git
pkg_irc_lib_repo = https://github.com/OtpChatBot/irc_lib
pkg_irc_lib_commit = master
PACKAGES += iris
pkg_iris_name = iris
pkg_iris_description = Iris Erlang binding
pkg_iris_homepage = https://github.com/project-iris/iris-erl
pkg_iris_fetch = git
pkg_iris_repo = https://github.com/project-iris/iris-erl
pkg_iris_commit = master
PACKAGES += iso8601
pkg_iso8601_name = iso8601
pkg_iso8601_description = Erlang ISO 8601 date formatter/parser
pkg_iso8601_homepage = https://github.com/seansawyer/erlang_iso8601
pkg_iso8601_fetch = git
pkg_iso8601_repo = https://github.com/seansawyer/erlang_iso8601
pkg_iso8601_commit = master
PACKAGES += itweet
pkg_itweet_name = itweet
pkg_itweet_description = Twitter Stream API on ibrowse
pkg_itweet_homepage = http://inaka.github.com/itweet/
pkg_itweet_fetch = git
pkg_itweet_repo = https://github.com/inaka/itweet
pkg_itweet_commit = v2.0
PACKAGES += jerg
pkg_jerg_name = jerg
pkg_jerg_description = JSON Schema to Erlang Records Generator
pkg_jerg_homepage = https://github.com/ddossot/jerg
pkg_jerg_fetch = git
pkg_jerg_repo = https://github.com/ddossot/jerg
pkg_jerg_commit = master
PACKAGES += jesse
pkg_jesse_name = jesse
pkg_jesse_description = jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang.
pkg_jesse_homepage = https://github.com/klarna/jesse
pkg_jesse_fetch = git
pkg_jesse_repo = https://github.com/klarna/jesse
pkg_jesse_commit = master
PACKAGES += jiffy
pkg_jiffy_name = jiffy
pkg_jiffy_description = JSON NIFs for Erlang.
pkg_jiffy_homepage = https://github.com/davisp/jiffy
pkg_jiffy_fetch = git
pkg_jiffy_repo = https://github.com/davisp/jiffy
pkg_jiffy_commit = master
PACKAGES += jiffy_v
pkg_jiffy_v_name = jiffy_v
pkg_jiffy_v_description = JSON validation utility
pkg_jiffy_v_homepage = https://github.com/shizzard/jiffy-v
pkg_jiffy_v_fetch = git
pkg_jiffy_v_repo = https://github.com/shizzard/jiffy-v
pkg_jiffy_v_commit = 0.3.3
PACKAGES += jobs
pkg_jobs_name = jobs
pkg_jobs_description = a Job scheduler for load regulation
pkg_jobs_homepage = https://github.com/esl/jobs
pkg_jobs_fetch = git
pkg_jobs_repo = https://github.com/esl/jobs
pkg_jobs_commit = 0.3
PACKAGES += joxa
pkg_joxa_name = joxa
pkg_joxa_description = A Modern Lisp for the Erlang VM
pkg_joxa_homepage = https://github.com/joxa/joxa
pkg_joxa_fetch = git
pkg_joxa_repo = https://github.com/joxa/joxa
pkg_joxa_commit = master
PACKAGES += jsonerl
pkg_jsonerl_name = jsonerl
pkg_jsonerl_description = yet another but slightly different erlang <-> json encoder/decoder
pkg_jsonerl_homepage = https://github.com/lambder/jsonerl
pkg_jsonerl_fetch = git
pkg_jsonerl_repo = https://github.com/lambder/jsonerl
pkg_jsonerl_commit = master
PACKAGES += json
pkg_json_name = json
pkg_json_description = a high level json library for erlang (17.0+)
pkg_json_homepage = https://github.com/talentdeficit/json
pkg_json_fetch = git
pkg_json_repo = https://github.com/talentdeficit/json
pkg_json_commit = master
PACKAGES += jsonpath
pkg_jsonpath_name = jsonpath
pkg_jsonpath_description = Fast Erlang JSON data retrieval and updates via javascript-like notation
pkg_jsonpath_homepage = https://github.com/GeneStevens/jsonpath
pkg_jsonpath_fetch = git
pkg_jsonpath_repo = https://github.com/GeneStevens/jsonpath
pkg_jsonpath_commit = master
PACKAGES += json_rec
pkg_json_rec_name = json_rec
pkg_json_rec_description = JSON to erlang record
pkg_json_rec_homepage = https://github.com/justinkirby/json_rec
pkg_json_rec_fetch = git
pkg_json_rec_repo = https://github.com/justinkirby/json_rec
pkg_json_rec_commit = master
PACKAGES += jsonx
pkg_jsonx_name = jsonx
pkg_jsonx_description = JSONX is an Erlang library for efficient decode and encode JSON, written in C.
pkg_jsonx_homepage = https://github.com/iskra/jsonx
pkg_jsonx_fetch = git
pkg_jsonx_repo = https://github.com/iskra/jsonx
pkg_jsonx_commit = master
PACKAGES += jsx
pkg_jsx_name = jsx
pkg_jsx_description = An Erlang application for consuming, producing and manipulating JSON.
pkg_jsx_homepage = https://github.com/talentdeficit/jsx
pkg_jsx_fetch = git
pkg_jsx_repo = https://github.com/talentdeficit/jsx
pkg_jsx_commit = master
PACKAGES += kafka
pkg_kafka_name = kafka
pkg_kafka_description = Kafka consumer and producer in Erlang
pkg_kafka_homepage = https://github.com/wooga/kafka-erlang
pkg_kafka_fetch = git
pkg_kafka_repo = https://github.com/wooga/kafka-erlang
pkg_kafka_commit = master
PACKAGES += kai
pkg_kai_name = kai
pkg_kai_description = DHT storage by Takeshi Inoue
pkg_kai_homepage = https://github.com/synrc/kai
pkg_kai_fetch = git
pkg_kai_repo = https://github.com/synrc/kai
pkg_kai_commit = master
PACKAGES += katja
pkg_katja_name = katja
pkg_katja_description = A simple Riemann client written in Erlang.
pkg_katja_homepage = https://github.com/nifoc/katja
pkg_katja_fetch = git
pkg_katja_repo = https://github.com/nifoc/katja
pkg_katja_commit = master
PACKAGES += kdht
pkg_kdht_name = kdht
pkg_kdht_description = kdht is an erlang DHT implementation
pkg_kdht_homepage = https://github.com/kevinlynx/kdht
pkg_kdht_fetch = git
pkg_kdht_repo = https://github.com/kevinlynx/kdht
pkg_kdht_commit = master
PACKAGES += key2value
pkg_key2value_name = key2value
pkg_key2value_description = Erlang 2-way map
pkg_key2value_homepage = https://github.com/okeuday/key2value
pkg_key2value_fetch = git
pkg_key2value_repo = https://github.com/okeuday/key2value
pkg_key2value_commit = master
PACKAGES += keys1value
pkg_keys1value_name = keys1value
pkg_keys1value_description = Erlang set associative map for key lists
pkg_keys1value_homepage = https://github.com/okeuday/keys1value
pkg_keys1value_fetch = git
pkg_keys1value_repo = https://github.com/okeuday/keys1value
pkg_keys1value_commit = master
PACKAGES += kinetic
pkg_kinetic_name = kinetic
pkg_kinetic_description = Erlang Kinesis Client
pkg_kinetic_homepage = https://github.com/AdRoll/kinetic
pkg_kinetic_fetch = git
pkg_kinetic_repo = https://github.com/AdRoll/kinetic
pkg_kinetic_commit = master
PACKAGES += kjell
pkg_kjell_name = kjell
pkg_kjell_description = Erlang Shell
pkg_kjell_homepage = https://github.com/karlll/kjell
pkg_kjell_fetch = git
pkg_kjell_repo = https://github.com/karlll/kjell
pkg_kjell_commit = master
PACKAGES += kraken
pkg_kraken_name = kraken
pkg_kraken_description = Distributed Pubsub Server for Realtime Apps
pkg_kraken_homepage = https://github.com/Asana/kraken
pkg_kraken_fetch = git
pkg_kraken_repo = https://github.com/Asana/kraken
pkg_kraken_commit = master
PACKAGES += kucumberl
pkg_kucumberl_name = kucumberl
pkg_kucumberl_description = A pure-erlang, open-source, implementation of Cucumber
pkg_kucumberl_homepage = https://github.com/openshine/kucumberl
pkg_kucumberl_fetch = git
pkg_kucumberl_repo = https://github.com/openshine/kucumberl
pkg_kucumberl_commit = master
PACKAGES += kvc
pkg_kvc_name = kvc
pkg_kvc_description = KVC - Key Value Coding for Erlang data structures
pkg_kvc_homepage = https://github.com/etrepum/kvc
pkg_kvc_fetch = git
pkg_kvc_repo = https://github.com/etrepum/kvc
pkg_kvc_commit = master
PACKAGES += kvlists
pkg_kvlists_name = kvlists
pkg_kvlists_description = Lists of key-value pairs (decoded JSON) in Erlang
pkg_kvlists_homepage = https://github.com/jcomellas/kvlists
pkg_kvlists_fetch = git
pkg_kvlists_repo = https://github.com/jcomellas/kvlists
pkg_kvlists_commit = master
PACKAGES += kvs
pkg_kvs_name = kvs
pkg_kvs_description = Container and Iterator
pkg_kvs_homepage = https://github.com/synrc/kvs
pkg_kvs_fetch = git
pkg_kvs_repo = https://github.com/synrc/kvs
pkg_kvs_commit = master
PACKAGES += lager_amqp_backend
pkg_lager_amqp_backend_name = lager_amqp_backend
pkg_lager_amqp_backend_description = AMQP RabbitMQ Lager backend
pkg_lager_amqp_backend_homepage = https://github.com/jbrisbin/lager_amqp_backend
pkg_lager_amqp_backend_fetch = git
pkg_lager_amqp_backend_repo = https://github.com/jbrisbin/lager_amqp_backend
pkg_lager_amqp_backend_commit = master
PACKAGES += lager
pkg_lager_name = lager
pkg_lager_description = A logging framework for Erlang/OTP.
pkg_lager_homepage = https://github.com/basho/lager
pkg_lager_fetch = git
pkg_lager_repo = https://github.com/basho/lager
pkg_lager_commit = master
PACKAGES += lager_syslog
pkg_lager_syslog_name = lager_syslog
pkg_lager_syslog_description = Syslog backend for lager
pkg_lager_syslog_homepage = https://github.com/basho/lager_syslog
pkg_lager_syslog_fetch = git
pkg_lager_syslog_repo = https://github.com/basho/lager_syslog
pkg_lager_syslog_commit = master
PACKAGES += lambdapad
pkg_lambdapad_name = lambdapad
pkg_lambdapad_description = Static site generator using Erlang. Yes, Erlang.
pkg_lambdapad_homepage = https://github.com/gar1t/lambdapad
pkg_lambdapad_fetch = git
pkg_lambdapad_repo = https://github.com/gar1t/lambdapad
pkg_lambdapad_commit = master
PACKAGES += lasp
pkg_lasp_name = lasp
pkg_lasp_description = A Language for Distributed, Eventually Consistent Computations
pkg_lasp_homepage = http://lasp-lang.org/
pkg_lasp_fetch = git
pkg_lasp_repo = https://github.com/lasp-lang/lasp
pkg_lasp_commit = master
PACKAGES += lasse
pkg_lasse_name = lasse
pkg_lasse_description = SSE handler for Cowboy
pkg_lasse_homepage = https://github.com/inaka/lasse
pkg_lasse_fetch = git
pkg_lasse_repo = https://github.com/inaka/lasse
pkg_lasse_commit = 0.1.0
PACKAGES += ldap
pkg_ldap_name = ldap
pkg_ldap_description = LDAP server written in Erlang
pkg_ldap_homepage = https://github.com/spawnproc/ldap
pkg_ldap_fetch = git
pkg_ldap_repo = https://github.com/spawnproc/ldap
pkg_ldap_commit = master
PACKAGES += lethink
pkg_lethink_name = lethink
pkg_lethink_description = erlang driver for rethinkdb
pkg_lethink_homepage = https://github.com/taybin/lethink
pkg_lethink_fetch = git
pkg_lethink_repo = https://github.com/taybin/lethink
pkg_lethink_commit = master
PACKAGES += lfe
pkg_lfe_name = lfe
pkg_lfe_description = Lisp Flavoured Erlang (LFE)
pkg_lfe_homepage = https://github.com/rvirding/lfe
pkg_lfe_fetch = git
pkg_lfe_repo = https://github.com/rvirding/lfe
pkg_lfe_commit = master
PACKAGES += ling
pkg_ling_name = ling
pkg_ling_description = Erlang on Xen
pkg_ling_homepage = https://github.com/cloudozer/ling
pkg_ling_fetch = git
pkg_ling_repo = https://github.com/cloudozer/ling
pkg_ling_commit = master
PACKAGES += live
pkg_live_name = live
pkg_live_description = Automated module and configuration reloader.
pkg_live_homepage = http://ninenines.eu
pkg_live_fetch = git
pkg_live_repo = https://github.com/ninenines/live
pkg_live_commit = master
PACKAGES += lmq
pkg_lmq_name = lmq
pkg_lmq_description = Lightweight Message Queue
pkg_lmq_homepage = https://github.com/iij/lmq
pkg_lmq_fetch = git
pkg_lmq_repo = https://github.com/iij/lmq
pkg_lmq_commit = master
PACKAGES += locker
pkg_locker_name = locker
pkg_locker_description = Atomic distributed 'check and set' for short-lived keys
pkg_locker_homepage = https://github.com/wooga/locker
pkg_locker_fetch = git
pkg_locker_repo = https://github.com/wooga/locker
pkg_locker_commit = master
PACKAGES += locks
pkg_locks_name = locks
pkg_locks_description = A scalable, deadlock-resolving resource locker
pkg_locks_homepage = https://github.com/uwiger/locks
pkg_locks_fetch = git
pkg_locks_repo = https://github.com/uwiger/locks
pkg_locks_commit = master
PACKAGES += log4erl
pkg_log4erl_name = log4erl
pkg_log4erl_description = A logger for erlang in the spirit of Log4J.
pkg_log4erl_homepage = https://github.com/ahmednawras/log4erl
pkg_log4erl_fetch = git
pkg_log4erl_repo = https://github.com/ahmednawras/log4erl
pkg_log4erl_commit = master
PACKAGES += lol
pkg_lol_name = lol
pkg_lol_description = Lisp on erLang, and programming is fun again
pkg_lol_homepage = https://github.com/b0oh/lol
pkg_lol_fetch = git
pkg_lol_repo = https://github.com/b0oh/lol
pkg_lol_commit = master
PACKAGES += lucid
pkg_lucid_name = lucid
pkg_lucid_description = HTTP/2 server written in Erlang
pkg_lucid_homepage = https://github.com/tatsuhiro-t/lucid
pkg_lucid_fetch = git
pkg_lucid_repo = https://github.com/tatsuhiro-t/lucid
pkg_lucid_commit = master
PACKAGES += luerl
pkg_luerl_name = luerl
pkg_luerl_description = Lua in Erlang
pkg_luerl_homepage = https://github.com/rvirding/luerl
pkg_luerl_fetch = git
pkg_luerl_repo = https://github.com/rvirding/luerl
pkg_luerl_commit = develop
PACKAGES += luwak
pkg_luwak_name = luwak
pkg_luwak_description = Large-object storage interface for Riak
pkg_luwak_homepage = https://github.com/basho/luwak
pkg_luwak_fetch = git
pkg_luwak_repo = https://github.com/basho/luwak
pkg_luwak_commit = master
PACKAGES += lux
pkg_lux_name = lux
pkg_lux_description = Lux (LUcid eXpect scripting) simplifies test automation and provides an Expect-style execution of commands
pkg_lux_homepage = https://github.com/hawk/lux
pkg_lux_fetch = git
pkg_lux_repo = https://github.com/hawk/lux
pkg_lux_commit = master
PACKAGES += machi
pkg_machi_name = machi
pkg_machi_description = Machi file store
pkg_machi_homepage = https://github.com/basho/machi
pkg_machi_fetch = git
pkg_machi_repo = https://github.com/basho/machi
pkg_machi_commit = master
PACKAGES += mad
pkg_mad_name = mad
pkg_mad_description = Small and Fast Rebar Replacement
pkg_mad_homepage = https://github.com/synrc/mad
pkg_mad_fetch = git
pkg_mad_repo = https://github.com/synrc/mad
pkg_mad_commit = master
PACKAGES += marina
pkg_marina_name = marina
pkg_marina_description = Non-blocking Erlang Cassandra CQL3 client
pkg_marina_homepage = https://github.com/lpgauth/marina
pkg_marina_fetch = git
pkg_marina_repo = https://github.com/lpgauth/marina
pkg_marina_commit = master
PACKAGES += mavg
pkg_mavg_name = mavg
pkg_mavg_description = Erlang :: Exponential moving average library
pkg_mavg_homepage = https://github.com/EchoTeam/mavg
pkg_mavg_fetch = git
pkg_mavg_repo = https://github.com/EchoTeam/mavg
pkg_mavg_commit = master
PACKAGES += mcd
pkg_mcd_name = mcd
pkg_mcd_description = Fast memcached protocol client in pure Erlang
pkg_mcd_homepage = https://github.com/EchoTeam/mcd
pkg_mcd_fetch = git
pkg_mcd_repo = https://github.com/EchoTeam/mcd
pkg_mcd_commit = master
PACKAGES += mcerlang
pkg_mcerlang_name = mcerlang
pkg_mcerlang_description = The McErlang model checker for Erlang
pkg_mcerlang_homepage = https://github.com/fredlund/McErlang
pkg_mcerlang_fetch = git
pkg_mcerlang_repo = https://github.com/fredlund/McErlang
pkg_mcerlang_commit = master
PACKAGES += mc_erl
pkg_mc_erl_name = mc_erl
pkg_mc_erl_description = mc-erl is a server for Minecraft 1.4.7 written in Erlang.
pkg_mc_erl_homepage = https://github.com/clonejo/mc-erl
pkg_mc_erl_fetch = git
pkg_mc_erl_repo = https://github.com/clonejo/mc-erl
pkg_mc_erl_commit = master
PACKAGES += meck
pkg_meck_name = meck
pkg_meck_description = A mocking library for Erlang
pkg_meck_homepage = https://github.com/eproxus/meck
pkg_meck_fetch = git
pkg_meck_repo = https://github.com/eproxus/meck
pkg_meck_commit = master
PACKAGES += mekao
pkg_mekao_name = mekao
pkg_mekao_description = SQL constructor
pkg_mekao_homepage = https://github.com/ddosia/mekao
pkg_mekao_fetch = git
pkg_mekao_repo = https://github.com/ddosia/mekao
pkg_mekao_commit = master
PACKAGES += memo
pkg_memo_name = memo
pkg_memo_description = Erlang memoization server
pkg_memo_homepage = https://github.com/tuncer/memo
pkg_memo_fetch = git
pkg_memo_repo = https://github.com/tuncer/memo
pkg_memo_commit = master
PACKAGES += merge_index
pkg_merge_index_name = merge_index
pkg_merge_index_description = MergeIndex is an Erlang library for storing ordered sets on disk. It is very similar to an SSTable (in Google's Bigtable) or an HFile (in Hadoop).
pkg_merge_index_homepage = https://github.com/basho/merge_index
pkg_merge_index_fetch = git
pkg_merge_index_repo = https://github.com/basho/merge_index
pkg_merge_index_commit = master
PACKAGES += merl
pkg_merl_name = merl
pkg_merl_description = Metaprogramming in Erlang
pkg_merl_homepage = https://github.com/richcarl/merl
pkg_merl_fetch = git
pkg_merl_repo = https://github.com/richcarl/merl
pkg_merl_commit = master
PACKAGES += mimetypes
pkg_mimetypes_name = mimetypes
pkg_mimetypes_description = Erlang MIME types library
pkg_mimetypes_homepage = https://github.com/spawngrid/mimetypes
pkg_mimetypes_fetch = git
pkg_mimetypes_repo = https://github.com/spawngrid/mimetypes
pkg_mimetypes_commit = master
PACKAGES += mixer
pkg_mixer_name = mixer
pkg_mixer_description = Mix in functions from other modules
pkg_mixer_homepage = https://github.com/chef/mixer
pkg_mixer_fetch = git
pkg_mixer_repo = https://github.com/chef/mixer
pkg_mixer_commit = master
PACKAGES += mochiweb
pkg_mochiweb_name = mochiweb
pkg_mochiweb_description = MochiWeb is an Erlang library for building lightweight HTTP servers.
pkg_mochiweb_homepage = https://github.com/mochi/mochiweb
pkg_mochiweb_fetch = git
pkg_mochiweb_repo = https://github.com/mochi/mochiweb
pkg_mochiweb_commit = master
PACKAGES += mochiweb_xpath
pkg_mochiweb_xpath_name = mochiweb_xpath
pkg_mochiweb_xpath_description = XPath support for mochiweb's html parser
pkg_mochiweb_xpath_homepage = https://github.com/retnuh/mochiweb_xpath
pkg_mochiweb_xpath_fetch = git
pkg_mochiweb_xpath_repo = https://github.com/retnuh/mochiweb_xpath
pkg_mochiweb_xpath_commit = master
PACKAGES += mockgyver
pkg_mockgyver_name = mockgyver
pkg_mockgyver_description = A mocking library for Erlang
pkg_mockgyver_homepage = https://github.com/klajo/mockgyver
pkg_mockgyver_fetch = git
pkg_mockgyver_repo = https://github.com/klajo/mockgyver
pkg_mockgyver_commit = master
PACKAGES += modlib
pkg_modlib_name = modlib
pkg_modlib_description = Web framework based on Erlang's inets httpd
pkg_modlib_homepage = https://github.com/gar1t/modlib
pkg_modlib_fetch = git
pkg_modlib_repo = https://github.com/gar1t/modlib
pkg_modlib_commit = master
PACKAGES += mongodb
pkg_mongodb_name = mongodb
pkg_mongodb_description = MongoDB driver for Erlang
pkg_mongodb_homepage = https://github.com/comtihon/mongodb-erlang
pkg_mongodb_fetch = git
pkg_mongodb_repo = https://github.com/comtihon/mongodb-erlang
pkg_mongodb_commit = master
PACKAGES += mongooseim
pkg_mongooseim_name = mongooseim
pkg_mongooseim_description = Jabber / XMPP server with focus on performance and scalability, by Erlang Solutions
pkg_mongooseim_homepage = https://www.erlang-solutions.com/products/mongooseim-massively-scalable-ejabberd-platform
pkg_mongooseim_fetch = git
pkg_mongooseim_repo = https://github.com/esl/MongooseIM
pkg_mongooseim_commit = master
PACKAGES += moyo
pkg_moyo_name = moyo
pkg_moyo_description = Erlang utility functions library
pkg_moyo_homepage = https://github.com/dwango/moyo
pkg_moyo_fetch = git
pkg_moyo_repo = https://github.com/dwango/moyo
pkg_moyo_commit = master
PACKAGES += msgpack
pkg_msgpack_name = msgpack
pkg_msgpack_description = MessagePack (de)serializer implementation for Erlang
pkg_msgpack_homepage = https://github.com/msgpack/msgpack-erlang
pkg_msgpack_fetch = git
pkg_msgpack_repo = https://github.com/msgpack/msgpack-erlang
pkg_msgpack_commit = master
PACKAGES += mu2
pkg_mu2_name = mu2
pkg_mu2_description = Erlang mutation testing tool
pkg_mu2_homepage = https://github.com/ramsay-t/mu2
pkg_mu2_fetch = git
pkg_mu2_repo = https://github.com/ramsay-t/mu2
pkg_mu2_commit = master
PACKAGES += mustache
pkg_mustache_name = mustache
pkg_mustache_description = Mustache template engine for Erlang.
pkg_mustache_homepage = https://github.com/mojombo/mustache.erl
pkg_mustache_fetch = git
pkg_mustache_repo = https://github.com/mojombo/mustache.erl
pkg_mustache_commit = master
PACKAGES += myproto
pkg_myproto_name = myproto
pkg_myproto_description = MySQL Server Protocol in Erlang
pkg_myproto_homepage = https://github.com/altenwald/myproto
pkg_myproto_fetch = git
pkg_myproto_repo = https://github.com/altenwald/myproto
pkg_myproto_commit = master
PACKAGES += mysql
pkg_mysql_name = mysql
pkg_mysql_description = Erlang MySQL Driver (from code.google.com)
pkg_mysql_homepage = https://github.com/dizzyd/erlang-mysql-driver
pkg_mysql_fetch = git
pkg_mysql_repo = https://github.com/dizzyd/erlang-mysql-driver
pkg_mysql_commit = master
PACKAGES += n2o
pkg_n2o_name = n2o
pkg_n2o_description = WebSocket Application Server
pkg_n2o_homepage = https://github.com/5HT/n2o
pkg_n2o_fetch = git
pkg_n2o_repo = https://github.com/5HT/n2o
pkg_n2o_commit = master
PACKAGES += nat_upnp
pkg_nat_upnp_name = nat_upnp
pkg_nat_upnp_description = Erlang library to map your internal port to an external using UNP IGD
pkg_nat_upnp_homepage = https://github.com/benoitc/nat_upnp
pkg_nat_upnp_fetch = git
pkg_nat_upnp_repo = https://github.com/benoitc/nat_upnp
pkg_nat_upnp_commit = master
PACKAGES += neo4j
pkg_neo4j_name = neo4j
pkg_neo4j_description = Erlang client library for Neo4J.
pkg_neo4j_homepage = https://github.com/dmitriid/neo4j-erlang
pkg_neo4j_fetch = git
pkg_neo4j_repo = https://github.com/dmitriid/neo4j-erlang
pkg_neo4j_commit = master
PACKAGES += neotoma
pkg_neotoma_name = neotoma
pkg_neotoma_description = Erlang library and packrat parser-generator for parsing expression grammars.
pkg_neotoma_homepage = https://github.com/seancribbs/neotoma
pkg_neotoma_fetch = git
pkg_neotoma_repo = https://github.com/seancribbs/neotoma
pkg_neotoma_commit = master
PACKAGES += newrelic
pkg_newrelic_name = newrelic
pkg_newrelic_description = Erlang library for sending metrics to New Relic
pkg_newrelic_homepage = https://github.com/wooga/newrelic-erlang
pkg_newrelic_fetch = git
pkg_newrelic_repo = https://github.com/wooga/newrelic-erlang
pkg_newrelic_commit = master
PACKAGES += nifty
pkg_nifty_name = nifty
pkg_nifty_description = Erlang NIF wrapper generator
pkg_nifty_homepage = https://github.com/parapluu/nifty
pkg_nifty_fetch = git
pkg_nifty_repo = https://github.com/parapluu/nifty
pkg_nifty_commit = master
PACKAGES += nitrogen_core
pkg_nitrogen_core_name = nitrogen_core
pkg_nitrogen_core_description = The core Nitrogen library.
pkg_nitrogen_core_homepage = http://nitrogenproject.com/
pkg_nitrogen_core_fetch = git
pkg_nitrogen_core_repo = https://github.com/nitrogen/nitrogen_core
pkg_nitrogen_core_commit = master
PACKAGES += nkbase
pkg_nkbase_name = nkbase
pkg_nkbase_description = NkBASE distributed database
pkg_nkbase_homepage = https://github.com/Nekso/nkbase
pkg_nkbase_fetch = git
pkg_nkbase_repo = https://github.com/Nekso/nkbase
pkg_nkbase_commit = develop
PACKAGES += nkdocker
pkg_nkdocker_name = nkdocker
pkg_nkdocker_description = Erlang Docker client
pkg_nkdocker_homepage = https://github.com/Nekso/nkdocker
pkg_nkdocker_fetch = git
pkg_nkdocker_repo = https://github.com/Nekso/nkdocker
pkg_nkdocker_commit = master
PACKAGES += nkpacket
pkg_nkpacket_name = nkpacket
pkg_nkpacket_description = Generic Erlang transport layer
pkg_nkpacket_homepage = https://github.com/Nekso/nkpacket
pkg_nkpacket_fetch = git
pkg_nkpacket_repo = https://github.com/Nekso/nkpacket
pkg_nkpacket_commit = master
PACKAGES += nodefinder
pkg_nodefinder_name = nodefinder
pkg_nodefinder_description = automatic node discovery via UDP multicast
pkg_nodefinder_homepage = https://github.com/erlanger/nodefinder
pkg_nodefinder_fetch = git
pkg_nodefinder_repo = https://github.com/okeuday/nodefinder
pkg_nodefinder_commit = master
PACKAGES += nprocreg
pkg_nprocreg_name = nprocreg
pkg_nprocreg_description = Minimal Distributed Erlang Process Registry
pkg_nprocreg_homepage = http://nitrogenproject.com/
pkg_nprocreg_fetch = git
pkg_nprocreg_repo = https://github.com/nitrogen/nprocreg
pkg_nprocreg_commit = master
PACKAGES += oauth2c
pkg_oauth2c_name = oauth2c
pkg_oauth2c_description = Erlang OAuth2 Client
pkg_oauth2c_homepage = https://github.com/kivra/oauth2_client
pkg_oauth2c_fetch = git
pkg_oauth2c_repo = https://github.com/kivra/oauth2_client
pkg_oauth2c_commit = master
PACKAGES += oauth2
pkg_oauth2_name = oauth2
pkg_oauth2_description = Erlang Oauth2 implementation
pkg_oauth2_homepage = https://github.com/kivra/oauth2
pkg_oauth2_fetch = git
pkg_oauth2_repo = https://github.com/kivra/oauth2
pkg_oauth2_commit = master
PACKAGES += oauth
pkg_oauth_name = oauth
pkg_oauth_description = An Erlang OAuth 1.0 implementation
pkg_oauth_homepage = https://github.com/tim/erlang-oauth
pkg_oauth_fetch = git
pkg_oauth_repo = https://github.com/tim/erlang-oauth
pkg_oauth_commit = master
PACKAGES += of_protocol
pkg_of_protocol_name = of_protocol
pkg_of_protocol_description = OpenFlow Protocol Library for Erlang
pkg_of_protocol_homepage = https://github.com/FlowForwarding/of_protocol
pkg_of_protocol_fetch = git
pkg_of_protocol_repo = https://github.com/FlowForwarding/of_protocol
pkg_of_protocol_commit = master
PACKAGES += openflow
pkg_openflow_name = openflow
pkg_openflow_description = An OpenFlow controller written in pure erlang
pkg_openflow_homepage = https://github.com/renatoaguiar/erlang-openflow
pkg_openflow_fetch = git
pkg_openflow_repo = https://github.com/renatoaguiar/erlang-openflow
pkg_openflow_commit = master
PACKAGES += openid
pkg_openid_name = openid
pkg_openid_description = Erlang OpenID
pkg_openid_homepage = https://github.com/brendonh/erl_openid
pkg_openid_fetch = git
pkg_openid_repo = https://github.com/brendonh/erl_openid
pkg_openid_commit = master
PACKAGES += openpoker
pkg_openpoker_name = openpoker
pkg_openpoker_description = Genesis Texas hold'em Game Server
pkg_openpoker_homepage = https://github.com/hpyhacking/openpoker
pkg_openpoker_fetch = git
pkg_openpoker_repo = https://github.com/hpyhacking/openpoker
pkg_openpoker_commit = master
PACKAGES += pal
pkg_pal_name = pal
pkg_pal_description = Pragmatic Authentication Library
pkg_pal_homepage = https://github.com/manifest/pal
pkg_pal_fetch = git
pkg_pal_repo = https://github.com/manifest/pal
pkg_pal_commit = master
PACKAGES += parse_trans
pkg_parse_trans_name = parse_trans
pkg_parse_trans_description = Parse transform utilities for Erlang
pkg_parse_trans_homepage = https://github.com/uwiger/parse_trans
pkg_parse_trans_fetch = git
pkg_parse_trans_repo = https://github.com/uwiger/parse_trans
pkg_parse_trans_commit = master
PACKAGES += parsexml
pkg_parsexml_name = parsexml
pkg_parsexml_description = Simple DOM XML parser with convenient and very simple API
pkg_parsexml_homepage = https://github.com/maxlapshin/parsexml
pkg_parsexml_fetch = git
pkg_parsexml_repo = https://github.com/maxlapshin/parsexml
pkg_parsexml_commit = master
PACKAGES += pegjs
pkg_pegjs_name = pegjs
pkg_pegjs_description = An implementation of PEG.js grammar for Erlang.
pkg_pegjs_homepage = https://github.com/dmitriid/pegjs
pkg_pegjs_fetch = git
pkg_pegjs_repo = https://github.com/dmitriid/pegjs
pkg_pegjs_commit = 0.3
PACKAGES += percept2
pkg_percept2_name = percept2
pkg_percept2_description = Concurrent profiling tool for Erlang
pkg_percept2_homepage = https://github.com/huiqing/percept2
pkg_percept2_fetch = git
pkg_percept2_repo = https://github.com/huiqing/percept2
pkg_percept2_commit = master
PACKAGES += pgsql
pkg_pgsql_name = pgsql
pkg_pgsql_description = Erlang PostgreSQL driver
pkg_pgsql_homepage = https://github.com/semiocast/pgsql
pkg_pgsql_fetch = git
pkg_pgsql_repo = https://github.com/semiocast/pgsql
pkg_pgsql_commit = master
PACKAGES += pkgx
pkg_pkgx_name = pkgx
pkg_pkgx_description = Build .deb packages from Erlang releases
pkg_pkgx_homepage = https://github.com/arjan/pkgx
pkg_pkgx_fetch = git
pkg_pkgx_repo = https://github.com/arjan/pkgx
pkg_pkgx_commit = master
PACKAGES += pkt
pkg_pkt_name = pkt
pkg_pkt_description = Erlang network protocol library
pkg_pkt_homepage = https://github.com/msantos/pkt
pkg_pkt_fetch = git
pkg_pkt_repo = https://github.com/msantos/pkt
pkg_pkt_commit = master
PACKAGES += plain_fsm
pkg_plain_fsm_name = plain_fsm
pkg_plain_fsm_description = A behaviour/support library for writing plain Erlang FSMs.
pkg_plain_fsm_homepage = https://github.com/uwiger/plain_fsm
pkg_plain_fsm_fetch = git
pkg_plain_fsm_repo = https://github.com/uwiger/plain_fsm
pkg_plain_fsm_commit = master
PACKAGES += plumtree
pkg_plumtree_name = plumtree
pkg_plumtree_description = Epidemic Broadcast Trees
pkg_plumtree_homepage = https://github.com/helium/plumtree
pkg_plumtree_fetch = git
pkg_plumtree_repo = https://github.com/helium/plumtree
pkg_plumtree_commit = master
PACKAGES += pmod_transform
pkg_pmod_transform_name = pmod_transform
pkg_pmod_transform_description = Parse transform for parameterized modules
pkg_pmod_transform_homepage = https://github.com/erlang/pmod_transform
pkg_pmod_transform_fetch = git
pkg_pmod_transform_repo = https://github.com/erlang/pmod_transform
pkg_pmod_transform_commit = master
PACKAGES += pobox
pkg_pobox_name = pobox
pkg_pobox_description = External buffer processes to protect against mailbox overflow in Erlang
pkg_pobox_homepage = https://github.com/ferd/pobox
pkg_pobox_fetch = git
pkg_pobox_repo = https://github.com/ferd/pobox
pkg_pobox_commit = master
PACKAGES += ponos
pkg_ponos_name = ponos
pkg_ponos_description = ponos is a simple yet powerful load generator written in erlang
pkg_ponos_homepage = https://github.com/klarna/ponos
pkg_ponos_fetch = git
pkg_ponos_repo = https://github.com/klarna/ponos
pkg_ponos_commit = master
PACKAGES += poolboy
pkg_poolboy_name = poolboy
pkg_poolboy_description = A hunky Erlang worker pool factory
pkg_poolboy_homepage = https://github.com/devinus/poolboy
pkg_poolboy_fetch = git
pkg_poolboy_repo = https://github.com/devinus/poolboy
pkg_poolboy_commit = master
PACKAGES += pooler
pkg_pooler_name = pooler
pkg_pooler_description = An OTP Process Pool Application
pkg_pooler_homepage = https://github.com/seth/pooler
pkg_pooler_fetch = git
pkg_pooler_repo = https://github.com/seth/pooler
pkg_pooler_commit = master
PACKAGES += pqueue
pkg_pqueue_name = pqueue
pkg_pqueue_description = Erlang Priority Queues
pkg_pqueue_homepage = https://github.com/okeuday/pqueue
pkg_pqueue_fetch = git
pkg_pqueue_repo = https://github.com/okeuday/pqueue
pkg_pqueue_commit = master
PACKAGES += procket
pkg_procket_name = procket
pkg_procket_description = Erlang interface to low level socket operations
pkg_procket_homepage = http://blog.listincomprehension.com/search/label/procket
pkg_procket_fetch = git
pkg_procket_repo = https://github.com/msantos/procket
pkg_procket_commit = master
PACKAGES += proper
pkg_proper_name = proper
pkg_proper_description = PropEr: a QuickCheck-inspired property-based testing tool for Erlang.
pkg_proper_homepage = http://proper.softlab.ntua.gr
pkg_proper_fetch = git
pkg_proper_repo = https://github.com/manopapad/proper
pkg_proper_commit = master
PACKAGES += prop
pkg_prop_name = prop
pkg_prop_description = An Erlang code scaffolding and generator system.
pkg_prop_homepage = https://github.com/nuex/prop
pkg_prop_fetch = git
pkg_prop_repo = https://github.com/nuex/prop
pkg_prop_commit = master
PACKAGES += props
pkg_props_name = props
pkg_props_description = Property structure library
pkg_props_homepage = https://github.com/greyarea/props
pkg_props_fetch = git
pkg_props_repo = https://github.com/greyarea/props
pkg_props_commit = master
PACKAGES += protobuffs
pkg_protobuffs_name = protobuffs
pkg_protobuffs_description = An implementation of Google's Protocol Buffers for Erlang, based on ngerakines/erlang_protobuffs.
pkg_protobuffs_homepage = https://github.com/basho/erlang_protobuffs
pkg_protobuffs_fetch = git
pkg_protobuffs_repo = https://github.com/basho/erlang_protobuffs
pkg_protobuffs_commit = master
PACKAGES += psycho
pkg_psycho_name = psycho
pkg_psycho_description = HTTP server that provides a WSGI-like interface for applications and middleware.
pkg_psycho_homepage = https://github.com/gar1t/psycho
pkg_psycho_fetch = git
pkg_psycho_repo = https://github.com/gar1t/psycho
pkg_psycho_commit = master
PACKAGES += ptrackerl
pkg_ptrackerl_name = ptrackerl
pkg_ptrackerl_description = Pivotal Tracker API Client written in Erlang
pkg_ptrackerl_homepage = https://github.com/inaka/ptrackerl
pkg_ptrackerl_fetch = git
pkg_ptrackerl_repo = https://github.com/inaka/ptrackerl
pkg_ptrackerl_commit = master
PACKAGES += purity
pkg_purity_name = purity
pkg_purity_description = A side-effect analyzer for Erlang
pkg_purity_homepage = https://github.com/mpitid/purity
pkg_purity_fetch = git
pkg_purity_repo = https://github.com/mpitid/purity
pkg_purity_commit = master
PACKAGES += push_service
pkg_push_service_name = push_service
pkg_push_service_description = Push service
pkg_push_service_homepage = https://github.com/hairyhum/push_service
pkg_push_service_fetch = git
pkg_push_service_repo = https://github.com/hairyhum/push_service
pkg_push_service_commit = master
PACKAGES += qdate
pkg_qdate_name = qdate
pkg_qdate_description = Date, time, and timezone parsing, formatting, and conversion for Erlang.
pkg_qdate_homepage = https://github.com/choptastic/qdate
pkg_qdate_fetch = git
pkg_qdate_repo = https://github.com/choptastic/qdate
pkg_qdate_commit = 0.4.0
PACKAGES += qrcode
pkg_qrcode_name = qrcode
pkg_qrcode_description = QR Code encoder in Erlang
pkg_qrcode_homepage = https://github.com/komone/qrcode
pkg_qrcode_fetch = git
pkg_qrcode_repo = https://github.com/komone/qrcode
pkg_qrcode_commit = master
PACKAGES += quest
pkg_quest_name = quest
pkg_quest_description = Learn Erlang through this set of challenges. An interactive system for getting to know Erlang.
pkg_quest_homepage = https://github.com/eriksoe/ErlangQuest
pkg_quest_fetch = git
pkg_quest_repo = https://github.com/eriksoe/ErlangQuest
pkg_quest_commit = master
PACKAGES += quickrand
pkg_quickrand_name = quickrand
pkg_quickrand_description = Quick Erlang Random Number Generation
pkg_quickrand_homepage = https://github.com/okeuday/quickrand
pkg_quickrand_fetch = git
pkg_quickrand_repo = https://github.com/okeuday/quickrand
pkg_quickrand_commit = master
PACKAGES += rabbit_exchange_type_riak
pkg_rabbit_exchange_type_riak_name = rabbit_exchange_type_riak
pkg_rabbit_exchange_type_riak_description = Custom RabbitMQ exchange type for sticking messages in Riak
pkg_rabbit_exchange_type_riak_homepage = https://github.com/jbrisbin/riak-exchange
pkg_rabbit_exchange_type_riak_fetch = git
pkg_rabbit_exchange_type_riak_repo = https://github.com/jbrisbin/riak-exchange
pkg_rabbit_exchange_type_riak_commit = master
PACKAGES += rabbit
pkg_rabbit_name = rabbit
pkg_rabbit_description = RabbitMQ Server
pkg_rabbit_homepage = https://www.rabbitmq.com/
pkg_rabbit_fetch = git
pkg_rabbit_repo = https://github.com/rabbitmq/rabbitmq-server.git
pkg_rabbit_commit = master
PACKAGES += rack
pkg_rack_name = rack
pkg_rack_description = Rack handler for erlang
pkg_rack_homepage = https://github.com/erlyvideo/rack
pkg_rack_fetch = git
pkg_rack_repo = https://github.com/erlyvideo/rack
pkg_rack_commit = master
PACKAGES += radierl
pkg_radierl_name = radierl
pkg_radierl_description = RADIUS protocol stack implemented in Erlang.
pkg_radierl_homepage = https://github.com/vances/radierl
pkg_radierl_fetch = git
pkg_radierl_repo = https://github.com/vances/radierl
pkg_radierl_commit = master
PACKAGES += rafter
pkg_rafter_name = rafter
pkg_rafter_description = An Erlang library application which implements the Raft consensus protocol
pkg_rafter_homepage = https://github.com/andrewjstone/rafter
pkg_rafter_fetch = git
pkg_rafter_repo = https://github.com/andrewjstone/rafter
pkg_rafter_commit = master
PACKAGES += ranch
pkg_ranch_name = ranch
pkg_ranch_description = Socket acceptor pool for TCP protocols.
pkg_ranch_homepage = http://ninenines.eu
pkg_ranch_fetch = git
pkg_ranch_repo = https://github.com/ninenines/ranch
pkg_ranch_commit = 1.1.0
PACKAGES += rbeacon
pkg_rbeacon_name = rbeacon
pkg_rbeacon_description = LAN discovery and presence in Erlang.
pkg_rbeacon_homepage = https://github.com/refuge/rbeacon
pkg_rbeacon_fetch = git
pkg_rbeacon_repo = https://github.com/refuge/rbeacon
pkg_rbeacon_commit = master
PACKAGES += rebar
pkg_rebar_name = rebar
pkg_rebar_description = Erlang build tool that makes it easy to compile and test Erlang applications, port drivers and releases.
pkg_rebar_homepage = http://www.rebar3.org
pkg_rebar_fetch = git
pkg_rebar_repo = https://github.com/rebar/rebar3
pkg_rebar_commit = master
PACKAGES += rebus
pkg_rebus_name = rebus
pkg_rebus_description = A stupid simple, internal, pub/sub event bus written in- and for Erlang.
pkg_rebus_homepage = https://github.com/olle/rebus
pkg_rebus_fetch = git
pkg_rebus_repo = https://github.com/olle/rebus
pkg_rebus_commit = master
PACKAGES += rec2json
pkg_rec2json_name = rec2json
pkg_rec2json_description = Compile erlang record definitions into modules to convert them to/from json easily.
pkg_rec2json_homepage = https://github.com/lordnull/rec2json
pkg_rec2json_fetch = git
pkg_rec2json_repo = https://github.com/lordnull/rec2json
pkg_rec2json_commit = master
PACKAGES += recon
pkg_recon_name = recon
pkg_recon_description = Collection of functions and scripts to debug Erlang in production.
pkg_recon_homepage = https://github.com/ferd/recon
pkg_recon_fetch = git
pkg_recon_repo = https://github.com/ferd/recon
pkg_recon_commit = 2.2.1
PACKAGES += record_info
pkg_record_info_name = record_info
pkg_record_info_description = Convert between record and proplist
pkg_record_info_homepage = https://github.com/bipthelin/erlang-record_info
pkg_record_info_fetch = git
pkg_record_info_repo = https://github.com/bipthelin/erlang-record_info
pkg_record_info_commit = master
PACKAGES += redgrid
pkg_redgrid_name = redgrid
pkg_redgrid_description = automatic Erlang node discovery via redis
pkg_redgrid_homepage = https://github.com/jkvor/redgrid
pkg_redgrid_fetch = git
pkg_redgrid_repo = https://github.com/jkvor/redgrid
pkg_redgrid_commit = master
PACKAGES += redo
pkg_redo_name = redo
pkg_redo_description = pipelined erlang redis client
pkg_redo_homepage = https://github.com/jkvor/redo
pkg_redo_fetch = git
pkg_redo_repo = https://github.com/jkvor/redo
pkg_redo_commit = master
PACKAGES += reltool_util
pkg_reltool_util_name = reltool_util
pkg_reltool_util_description = Erlang reltool utility functionality application
pkg_reltool_util_homepage = https://github.com/okeuday/reltool_util
pkg_reltool_util_fetch = git
pkg_reltool_util_repo = https://github.com/okeuday/reltool_util
pkg_reltool_util_commit = master
PACKAGES += relx
pkg_relx_name = relx
pkg_relx_description = Sane, simple release creation for Erlang
pkg_relx_homepage = https://github.com/erlware/relx
pkg_relx_fetch = git
pkg_relx_repo = https://github.com/erlware/relx
pkg_relx_commit = master
PACKAGES += resource_discovery
pkg_resource_discovery_name = resource_discovery
pkg_resource_discovery_description = An application used to dynamically discover resources present in an Erlang node cluster.
pkg_resource_discovery_homepage = http://erlware.org/
pkg_resource_discovery_fetch = git
pkg_resource_discovery_repo = https://github.com/erlware/resource_discovery
pkg_resource_discovery_commit = master
PACKAGES += restc
pkg_restc_name = restc
pkg_restc_description = Erlang Rest Client
pkg_restc_homepage = https://github.com/kivra/restclient
pkg_restc_fetch = git
pkg_restc_repo = https://github.com/kivra/restclient
pkg_restc_commit = master
PACKAGES += rfc4627_jsonrpc
pkg_rfc4627_jsonrpc_name = rfc4627_jsonrpc
pkg_rfc4627_jsonrpc_description = Erlang RFC4627 (JSON) codec and JSON-RPC server implementation.
pkg_rfc4627_jsonrpc_homepage = https://github.com/tonyg/erlang-rfc4627
pkg_rfc4627_jsonrpc_fetch = git
pkg_rfc4627_jsonrpc_repo = https://github.com/tonyg/erlang-rfc4627
pkg_rfc4627_jsonrpc_commit = master
PACKAGES += riakc
pkg_riakc_name = riakc
pkg_riakc_description = Erlang clients for Riak.
pkg_riakc_homepage = https://github.com/basho/riak-erlang-client
pkg_riakc_fetch = git
pkg_riakc_repo = https://github.com/basho/riak-erlang-client
pkg_riakc_commit = master
PACKAGES += riak_core
pkg_riak_core_name = riak_core
pkg_riak_core_description = Distributed systems infrastructure used by Riak.
pkg_riak_core_homepage = https://github.com/basho/riak_core
pkg_riak_core_fetch = git
pkg_riak_core_repo = https://github.com/basho/riak_core
pkg_riak_core_commit = master
PACKAGES += riak_dt
pkg_riak_dt_name = riak_dt
pkg_riak_dt_description = Convergent replicated datatypes in Erlang
pkg_riak_dt_homepage = https://github.com/basho/riak_dt
pkg_riak_dt_fetch = git
pkg_riak_dt_repo = https://github.com/basho/riak_dt
pkg_riak_dt_commit = master
PACKAGES += riak_ensemble
pkg_riak_ensemble_name = riak_ensemble
pkg_riak_ensemble_description = Multi-Paxos framework in Erlang
pkg_riak_ensemble_homepage = https://github.com/basho/riak_ensemble
pkg_riak_ensemble_fetch = git
pkg_riak_ensemble_repo = https://github.com/basho/riak_ensemble
pkg_riak_ensemble_commit = master
PACKAGES += riakhttpc
pkg_riakhttpc_name = riakhttpc
pkg_riakhttpc_description = Riak Erlang client using the HTTP interface
pkg_riakhttpc_homepage = https://github.com/basho/riak-erlang-http-client
pkg_riakhttpc_fetch = git
pkg_riakhttpc_repo = https://github.com/basho/riak-erlang-http-client
pkg_riakhttpc_commit = master
PACKAGES += riak_kv
pkg_riak_kv_name = riak_kv
pkg_riak_kv_description = Riak Key/Value Store
pkg_riak_kv_homepage = https://github.com/basho/riak_kv
pkg_riak_kv_fetch = git
pkg_riak_kv_repo = https://github.com/basho/riak_kv
pkg_riak_kv_commit = master
PACKAGES += riaknostic
pkg_riaknostic_name = riaknostic
pkg_riaknostic_description = A diagnostic tool for Riak installations, to find common errors asap
pkg_riaknostic_homepage = https://github.com/basho/riaknostic
pkg_riaknostic_fetch = git
pkg_riaknostic_repo = https://github.com/basho/riaknostic
pkg_riaknostic_commit = master
PACKAGES += riak_pg
pkg_riak_pg_name = riak_pg
pkg_riak_pg_description = Distributed process groups with riak_core.
pkg_riak_pg_homepage = https://github.com/cmeiklejohn/riak_pg
pkg_riak_pg_fetch = git
pkg_riak_pg_repo = https://github.com/cmeiklejohn/riak_pg
pkg_riak_pg_commit = master
PACKAGES += riak_pipe
pkg_riak_pipe_name = riak_pipe
pkg_riak_pipe_description = Riak Pipelines
pkg_riak_pipe_homepage = https://github.com/basho/riak_pipe
pkg_riak_pipe_fetch = git
pkg_riak_pipe_repo = https://github.com/basho/riak_pipe
pkg_riak_pipe_commit = master
PACKAGES += riakpool
pkg_riakpool_name = riakpool
pkg_riakpool_description = erlang riak client pool
pkg_riakpool_homepage = https://github.com/dweldon/riakpool
pkg_riakpool_fetch = git
pkg_riakpool_repo = https://github.com/dweldon/riakpool
pkg_riakpool_commit = master
PACKAGES += riak_sysmon
pkg_riak_sysmon_name = riak_sysmon
pkg_riak_sysmon_description = Simple OTP app for managing Erlang VM system_monitor event messages
pkg_riak_sysmon_homepage = https://github.com/basho/riak_sysmon
pkg_riak_sysmon_fetch = git
pkg_riak_sysmon_repo = https://github.com/basho/riak_sysmon
pkg_riak_sysmon_commit = master
PACKAGES += riak_test
pkg_riak_test_name = riak_test
pkg_riak_test_description = I'm in your cluster, testing your riaks
pkg_riak_test_homepage = https://github.com/basho/riak_test
pkg_riak_test_fetch = git
pkg_riak_test_repo = https://github.com/basho/riak_test
pkg_riak_test_commit = master
PACKAGES += rivus_cep
pkg_rivus_cep_name = rivus_cep
pkg_rivus_cep_description = Complex event processing in Erlang
pkg_rivus_cep_homepage = https://github.com/vascokk/rivus_cep
pkg_rivus_cep_fetch = git
pkg_rivus_cep_repo = https://github.com/vascokk/rivus_cep
pkg_rivus_cep_commit = master
PACKAGES += rlimit
pkg_rlimit_name = rlimit
pkg_rlimit_description = Magnus Klaar's rate limiter code from etorrent
pkg_rlimit_homepage = https://github.com/jlouis/rlimit
pkg_rlimit_fetch = git
pkg_rlimit_repo = https://github.com/jlouis/rlimit
pkg_rlimit_commit = master
PACKAGES += safetyvalve
pkg_safetyvalve_name = safetyvalve
pkg_safetyvalve_description = A safety valve for your erlang node
pkg_safetyvalve_homepage = https://github.com/jlouis/safetyvalve
pkg_safetyvalve_fetch = git
pkg_safetyvalve_repo = https://github.com/jlouis/safetyvalve
pkg_safetyvalve_commit = master
PACKAGES += seestar
pkg_seestar_name = seestar
pkg_seestar_description = The Erlang client for Cassandra 1.2+ binary protocol
pkg_seestar_homepage = https://github.com/iamaleksey/seestar
pkg_seestar_fetch = git
pkg_seestar_repo = https://github.com/iamaleksey/seestar
pkg_seestar_commit = master
PACKAGES += service
pkg_service_name = service
pkg_service_description = A minimal Erlang behavior for creating CloudI internal services
pkg_service_homepage = http://cloudi.org/
pkg_service_fetch = git
pkg_service_repo = https://github.com/CloudI/service
pkg_service_commit = master
PACKAGES += setup
pkg_setup_name = setup
pkg_setup_description = Generic setup utility for Erlang-based systems
pkg_setup_homepage = https://github.com/uwiger/setup
pkg_setup_fetch = git
pkg_setup_repo = https://github.com/uwiger/setup
pkg_setup_commit = master
PACKAGES += sext
pkg_sext_name = sext
pkg_sext_description = Sortable Erlang Term Serialization
pkg_sext_homepage = https://github.com/uwiger/sext
pkg_sext_fetch = git
pkg_sext_repo = https://github.com/uwiger/sext
pkg_sext_commit = master
PACKAGES += sfmt
pkg_sfmt_name = sfmt
pkg_sfmt_description = SFMT pseudo random number generator for Erlang.
pkg_sfmt_homepage = https://github.com/jj1bdx/sfmt-erlang
pkg_sfmt_fetch = git
pkg_sfmt_repo = https://github.com/jj1bdx/sfmt-erlang
pkg_sfmt_commit = master
PACKAGES += sgte
pkg_sgte_name = sgte
pkg_sgte_description = A simple Erlang Template Engine
pkg_sgte_homepage = https://github.com/filippo/sgte
pkg_sgte_fetch = git
pkg_sgte_repo = https://github.com/filippo/sgte
pkg_sgte_commit = master
PACKAGES += sheriff
pkg_sheriff_name = sheriff
pkg_sheriff_description = Parse transform for type based validation.
pkg_sheriff_homepage = http://ninenines.eu
pkg_sheriff_fetch = git
pkg_sheriff_repo = https://github.com/extend/sheriff
pkg_sheriff_commit = master
PACKAGES += shotgun
pkg_shotgun_name = shotgun
pkg_shotgun_description = better than just a gun
pkg_shotgun_homepage = https://github.com/inaka/shotgun
pkg_shotgun_fetch = git
pkg_shotgun_repo = https://github.com/inaka/shotgun
pkg_shotgun_commit = 0.1.0
PACKAGES += sidejob
pkg_sidejob_name = sidejob
pkg_sidejob_description = Parallel worker and capacity limiting library for Erlang
pkg_sidejob_homepage = https://github.com/basho/sidejob
pkg_sidejob_fetch = git
pkg_sidejob_repo = https://github.com/basho/sidejob
pkg_sidejob_commit = master
PACKAGES += sieve
pkg_sieve_name = sieve
pkg_sieve_description = sieve is a simple TCP routing proxy (layer 7) in erlang
pkg_sieve_homepage = https://github.com/benoitc/sieve
pkg_sieve_fetch = git
pkg_sieve_repo = https://github.com/benoitc/sieve
pkg_sieve_commit = master
PACKAGES += sighandler
pkg_sighandler_name = sighandler
pkg_sighandler_description = Handle UNIX signals in Er lang
pkg_sighandler_homepage = https://github.com/jkingsbery/sighandler
pkg_sighandler_fetch = git
pkg_sighandler_repo = https://github.com/jkingsbery/sighandler
pkg_sighandler_commit = master
PACKAGES += simhash
pkg_simhash_name = simhash
pkg_simhash_description = Simhashing for Erlang -- hashing algorithm to find near-duplicates in binary data.
pkg_simhash_homepage = https://github.com/ferd/simhash
pkg_simhash_fetch = git
pkg_simhash_repo = https://github.com/ferd/simhash
pkg_simhash_commit = master
PACKAGES += simple_bridge
pkg_simple_bridge_name = simple_bridge
pkg_simple_bridge_description = A simple, standardized interface library to Erlang HTTP Servers.
pkg_simple_bridge_homepage = https://github.com/nitrogen/simple_bridge
pkg_simple_bridge_fetch = git
pkg_simple_bridge_repo = https://github.com/nitrogen/simple_bridge
pkg_simple_bridge_commit = master
PACKAGES += simple_oauth2
pkg_simple_oauth2_name = simple_oauth2
pkg_simple_oauth2_description = Simple erlang OAuth2 client module for any http server framework (Google, Facebook, Yandex, Vkontakte are preconfigured)
pkg_simple_oauth2_homepage = https://github.com/virtan/simple_oauth2
pkg_simple_oauth2_fetch = git
pkg_simple_oauth2_repo = https://github.com/virtan/simple_oauth2
pkg_simple_oauth2_commit = master
PACKAGES += skel
pkg_skel_name = skel
pkg_skel_description = A Streaming Process-based Skeleton Library for Erlang
pkg_skel_homepage = https://github.com/ParaPhrase/skel
pkg_skel_fetch = git
pkg_skel_repo = https://github.com/ParaPhrase/skel
pkg_skel_commit = master
PACKAGES += smother
pkg_smother_name = smother
pkg_smother_description = Extended code coverage metrics for Erlang.
pkg_smother_homepage = https://ramsay-t.github.io/Smother/
pkg_smother_fetch = git
pkg_smother_repo = https://github.com/ramsay-t/Smother
pkg_smother_commit = master
PACKAGES += social
pkg_social_name = social
pkg_social_description = Cowboy handler for social login via OAuth2 providers
pkg_social_homepage = https://github.com/dvv/social
pkg_social_fetch = git
pkg_social_repo = https://github.com/dvv/social
pkg_social_commit = master
PACKAGES += spapi_router
pkg_spapi_router_name = spapi_router
pkg_spapi_router_description = Partially-connected Erlang clustering
pkg_spapi_router_homepage = https://github.com/spilgames/spapi-router
pkg_spapi_router_fetch = git
pkg_spapi_router_repo = https://github.com/spilgames/spapi-router
pkg_spapi_router_commit = master
PACKAGES += sqerl
pkg_sqerl_name = sqerl
pkg_sqerl_description = An Erlang-flavoured SQL DSL
pkg_sqerl_homepage = https://github.com/hairyhum/sqerl
pkg_sqerl_fetch = git
pkg_sqerl_repo = https://github.com/hairyhum/sqerl
pkg_sqerl_commit = master
PACKAGES += srly
pkg_srly_name = srly
pkg_srly_description = Native Erlang Unix serial interface
pkg_srly_homepage = https://github.com/msantos/srly
pkg_srly_fetch = git
pkg_srly_repo = https://github.com/msantos/srly
pkg_srly_commit = master
PACKAGES += sshrpc
pkg_sshrpc_name = sshrpc
pkg_sshrpc_description = Erlang SSH RPC module (experimental)
pkg_sshrpc_homepage = https://github.com/jj1bdx/sshrpc
pkg_sshrpc_fetch = git
pkg_sshrpc_repo = https://github.com/jj1bdx/sshrpc
pkg_sshrpc_commit = master
PACKAGES += stable
pkg_stable_name = stable
pkg_stable_description = Library of assorted helpers for Cowboy web server.
pkg_stable_homepage = https://github.com/dvv/stable
pkg_stable_fetch = git
pkg_stable_repo = https://github.com/dvv/stable
pkg_stable_commit = master
PACKAGES += statebox
pkg_statebox_name = statebox
pkg_statebox_description = Erlang state monad with merge/conflict-resolution capabilities. Useful for Riak.
pkg_statebox_homepage = https://github.com/mochi/statebox
pkg_statebox_fetch = git
pkg_statebox_repo = https://github.com/mochi/statebox
pkg_statebox_commit = master
PACKAGES += statebox_riak
pkg_statebox_riak_name = statebox_riak
pkg_statebox_riak_description = Convenience library that makes it easier to use statebox with riak, extracted from best practices in our production code at Mochi Media.
pkg_statebox_riak_homepage = https://github.com/mochi/statebox_riak
pkg_statebox_riak_fetch = git
pkg_statebox_riak_repo = https://github.com/mochi/statebox_riak
pkg_statebox_riak_commit = master
PACKAGES += statman
pkg_statman_name = statman
pkg_statman_description = Efficiently collect massive volumes of metrics inside the Erlang VM
pkg_statman_homepage = https://github.com/knutin/statman
pkg_statman_fetch = git
pkg_statman_repo = https://github.com/knutin/statman
pkg_statman_commit = master
PACKAGES += statsderl
pkg_statsderl_name = statsderl
pkg_statsderl_description = StatsD client (erlang)
pkg_statsderl_homepage = https://github.com/lpgauth/statsderl
pkg_statsderl_fetch = git
pkg_statsderl_repo = https://github.com/lpgauth/statsderl
pkg_statsderl_commit = master
PACKAGES += stdinout_pool
pkg_stdinout_pool_name = stdinout_pool
pkg_stdinout_pool_description = stdinout_pool : stuff goes in, stuff goes out. there's never any miscommunication.
pkg_stdinout_pool_homepage = https://github.com/mattsta/erlang-stdinout-pool
pkg_stdinout_pool_fetch = git
pkg_stdinout_pool_repo = https://github.com/mattsta/erlang-stdinout-pool
pkg_stdinout_pool_commit = master
PACKAGES += stockdb
pkg_stockdb_name = stockdb
pkg_stockdb_description = Database for storing Stock Exchange quotes in erlang
pkg_stockdb_homepage = https://github.com/maxlapshin/stockdb
pkg_stockdb_fetch = git
pkg_stockdb_repo = https://github.com/maxlapshin/stockdb
pkg_stockdb_commit = master
PACKAGES += stripe
pkg_stripe_name = stripe
pkg_stripe_description = Erlang interface to the stripe.com API
pkg_stripe_homepage = https://github.com/mattsta/stripe-erlang
pkg_stripe_fetch = git
pkg_stripe_repo = https://github.com/mattsta/stripe-erlang
pkg_stripe_commit = v1
PACKAGES += surrogate
pkg_surrogate_name = surrogate
pkg_surrogate_description = Proxy server written in erlang. Supports reverse proxy load balancing and forward proxy with http (including CONNECT), socks4, socks5, and transparent proxy modes.
pkg_surrogate_homepage = https://github.com/skruger/Surrogate
pkg_surrogate_fetch = git
pkg_surrogate_repo = https://github.com/skruger/Surrogate
pkg_surrogate_commit = master
PACKAGES += swab
pkg_swab_name = swab
pkg_swab_description = General purpose buffer handling module
pkg_swab_homepage = https://github.com/crownedgrouse/swab
pkg_swab_fetch = git
pkg_swab_repo = https://github.com/crownedgrouse/swab
pkg_swab_commit = master
PACKAGES += swarm
pkg_swarm_name = swarm
pkg_swarm_description = Fast and simple acceptor pool for Erlang
pkg_swarm_homepage = https://github.com/jeremey/swarm
pkg_swarm_fetch = git
pkg_swarm_repo = https://github.com/jeremey/swarm
pkg_swarm_commit = master
PACKAGES += switchboard
pkg_switchboard_name = switchboard
pkg_switchboard_description = A framework for processing email using worker plugins.
pkg_switchboard_homepage = https://github.com/thusfresh/switchboard
pkg_switchboard_fetch = git
pkg_switchboard_repo = https://github.com/thusfresh/switchboard
pkg_switchboard_commit = master
PACKAGES += sync
pkg_sync_name = sync
pkg_sync_description = On-the-fly recompiling and reloading in Erlang.
pkg_sync_homepage = https://github.com/rustyio/sync
pkg_sync_fetch = git
pkg_sync_repo = https://github.com/rustyio/sync
pkg_sync_commit = master
PACKAGES += syn
pkg_syn_name = syn
pkg_syn_description = A global process registry for Erlang.
pkg_syn_homepage = https://github.com/ostinelli/syn
pkg_syn_fetch = git
pkg_syn_repo = https://github.com/ostinelli/syn
pkg_syn_commit = master
PACKAGES += syntaxerl
pkg_syntaxerl_name = syntaxerl
pkg_syntaxerl_description = Syntax checker for Erlang
pkg_syntaxerl_homepage = https://github.com/ten0s/syntaxerl
pkg_syntaxerl_fetch = git
pkg_syntaxerl_repo = https://github.com/ten0s/syntaxerl
pkg_syntaxerl_commit = master
PACKAGES += syslog
pkg_syslog_name = syslog
pkg_syslog_description = Erlang port driver for interacting with syslog via syslog(3)
pkg_syslog_homepage = https://github.com/Vagabond/erlang-syslog
pkg_syslog_fetch = git
pkg_syslog_repo = https://github.com/Vagabond/erlang-syslog
pkg_syslog_commit = master
PACKAGES += taskforce
pkg_taskforce_name = taskforce
pkg_taskforce_description = Erlang worker pools for controlled parallelisation of arbitrary tasks.
pkg_taskforce_homepage = https://github.com/g-andrade/taskforce
pkg_taskforce_fetch = git
pkg_taskforce_repo = https://github.com/g-andrade/taskforce
pkg_taskforce_commit = master
PACKAGES += tddreloader
pkg_tddreloader_name = tddreloader
pkg_tddreloader_description = Shell utility for recompiling, reloading, and testing code as it changes
pkg_tddreloader_homepage = https://github.com/version2beta/tddreloader
pkg_tddreloader_fetch = git
pkg_tddreloader_repo = https://github.com/version2beta/tddreloader
pkg_tddreloader_commit = master
PACKAGES += tempo
pkg_tempo_name = tempo
pkg_tempo_description = NIF-based date and time parsing and formatting for Erlang.
pkg_tempo_homepage = https://github.com/selectel/tempo
pkg_tempo_fetch = git
pkg_tempo_repo = https://github.com/selectel/tempo
pkg_tempo_commit = master
PACKAGES += ticktick
pkg_ticktick_name = ticktick
pkg_ticktick_description = Ticktick is an id generator for message service.
pkg_ticktick_homepage = https://github.com/ericliang/ticktick
pkg_ticktick_fetch = git
pkg_ticktick_repo = https://github.com/ericliang/ticktick
pkg_ticktick_commit = master
PACKAGES += tinymq
pkg_tinymq_name = tinymq
pkg_tinymq_description = TinyMQ - a diminutive, in-memory message queue
pkg_tinymq_homepage = https://github.com/ChicagoBoss/tinymq
pkg_tinymq_fetch = git
pkg_tinymq_repo = https://github.com/ChicagoBoss/tinymq
pkg_tinymq_commit = master
PACKAGES += tinymt
pkg_tinymt_name = tinymt
pkg_tinymt_description = TinyMT pseudo random number generator for Erlang.
pkg_tinymt_homepage = https://github.com/jj1bdx/tinymt-erlang
pkg_tinymt_fetch = git
pkg_tinymt_repo = https://github.com/jj1bdx/tinymt-erlang
pkg_tinymt_commit = master
PACKAGES += tirerl
pkg_tirerl_name = tirerl
pkg_tirerl_description = Erlang interface to Elastic Search
pkg_tirerl_homepage = https://github.com/inaka/tirerl
pkg_tirerl_fetch = git
pkg_tirerl_repo = https://github.com/inaka/tirerl
pkg_tirerl_commit = master
PACKAGES += traffic_tools
pkg_traffic_tools_name = traffic_tools
pkg_traffic_tools_description = Simple traffic limiting library
pkg_traffic_tools_homepage = https://github.com/systra/traffic_tools
pkg_traffic_tools_fetch = git
pkg_traffic_tools_repo = https://github.com/systra/traffic_tools
pkg_traffic_tools_commit = master
PACKAGES += trails
pkg_trails_name = trails
pkg_trails_description = A couple of improvements over Cowboy Routes
pkg_trails_homepage = http://inaka.github.io/cowboy-trails/
pkg_trails_fetch = git
pkg_trails_repo = https://github.com/inaka/cowboy-trails
pkg_trails_commit = master
PACKAGES += trane
pkg_trane_name = trane
pkg_trane_description = SAX style broken HTML parser in Erlang
pkg_trane_homepage = https://github.com/massemanet/trane
pkg_trane_fetch = git
pkg_trane_repo = https://github.com/massemanet/trane
pkg_trane_commit = master
PACKAGES += transit
pkg_transit_name = transit
pkg_transit_description = transit format for erlang
pkg_transit_homepage = https://github.com/isaiah/transit-erlang
pkg_transit_fetch = git
pkg_transit_repo = https://github.com/isaiah/transit-erlang
pkg_transit_commit = master
PACKAGES += trie
pkg_trie_name = trie
pkg_trie_description = Erlang Trie Implementation
pkg_trie_homepage = https://github.com/okeuday/trie
pkg_trie_fetch = git
pkg_trie_repo = https://github.com/okeuday/trie
pkg_trie_commit = master
PACKAGES += triq
pkg_triq_name = triq
pkg_triq_description = Trifork QuickCheck
pkg_triq_homepage = https://github.com/krestenkrab/triq
pkg_triq_fetch = git
pkg_triq_repo = https://github.com/krestenkrab/triq
pkg_triq_commit = master
PACKAGES += tunctl
pkg_tunctl_name = tunctl
pkg_tunctl_description = Erlang TUN/TAP interface
pkg_tunctl_homepage = https://github.com/msantos/tunctl
pkg_tunctl_fetch = git
pkg_tunctl_repo = https://github.com/msantos/tunctl
pkg_tunctl_commit = master
PACKAGES += twerl
pkg_twerl_name = twerl
pkg_twerl_description = Erlang client for the Twitter Streaming API
pkg_twerl_homepage = https://github.com/lucaspiller/twerl
pkg_twerl_fetch = git
pkg_twerl_repo = https://github.com/lucaspiller/twerl
pkg_twerl_commit = oauth
PACKAGES += twitter_erlang
pkg_twitter_erlang_name = twitter_erlang
pkg_twitter_erlang_description = An Erlang twitter client
pkg_twitter_erlang_homepage = https://github.com/ngerakines/erlang_twitter
pkg_twitter_erlang_fetch = git
pkg_twitter_erlang_repo = https://github.com/ngerakines/erlang_twitter
pkg_twitter_erlang_commit = master
PACKAGES += ucol_nif
pkg_ucol_nif_name = ucol_nif
pkg_ucol_nif_description = ICU based collation Erlang module
pkg_ucol_nif_homepage = https://github.com/refuge/ucol_nif
pkg_ucol_nif_fetch = git
pkg_ucol_nif_repo = https://github.com/refuge/ucol_nif
pkg_ucol_nif_commit = master
PACKAGES += unicorn
pkg_unicorn_name = unicorn
pkg_unicorn_description = Generic configuration server
pkg_unicorn_homepage = https://github.com/shizzard/unicorn
pkg_unicorn_fetch = git
pkg_unicorn_repo = https://github.com/shizzard/unicorn
pkg_unicorn_commit = 0.3.0
PACKAGES += unsplit
pkg_unsplit_name = unsplit
pkg_unsplit_description = Resolves conflicts in Mnesia after network splits
pkg_unsplit_homepage = https://github.com/uwiger/unsplit
pkg_unsplit_fetch = git
pkg_unsplit_repo = https://github.com/uwiger/unsplit
pkg_unsplit_commit = master
PACKAGES += uuid
pkg_uuid_name = uuid
pkg_uuid_description = Erlang UUID Implementation
pkg_uuid_homepage = https://github.com/okeuday/uuid
pkg_uuid_fetch = git
pkg_uuid_repo = https://github.com/okeuday/uuid
pkg_uuid_commit = v1.4.0
PACKAGES += ux
pkg_ux_name = ux
pkg_ux_description = Unicode eXtention for Erlang (Strings, Collation)
pkg_ux_homepage = https://github.com/erlang-unicode/ux
pkg_ux_fetch = git
pkg_ux_repo = https://github.com/erlang-unicode/ux
pkg_ux_commit = master
PACKAGES += vert
pkg_vert_name = vert
pkg_vert_description = Erlang binding to libvirt virtualization API
pkg_vert_homepage = https://github.com/msantos/erlang-libvirt
pkg_vert_fetch = git
pkg_vert_repo = https://github.com/msantos/erlang-libvirt
pkg_vert_commit = master
PACKAGES += verx
pkg_verx_name = verx
pkg_verx_description = Erlang implementation of the libvirtd remote protocol
pkg_verx_homepage = https://github.com/msantos/verx
pkg_verx_fetch = git
pkg_verx_repo = https://github.com/msantos/verx
pkg_verx_commit = master
PACKAGES += vmq_acl
pkg_vmq_acl_name = vmq_acl
pkg_vmq_acl_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_acl_homepage = https://verne.mq/
pkg_vmq_acl_fetch = git
pkg_vmq_acl_repo = https://github.com/erlio/vmq_acl
pkg_vmq_acl_commit = master
PACKAGES += vmq_bridge
pkg_vmq_bridge_name = vmq_bridge
pkg_vmq_bridge_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_bridge_homepage = https://verne.mq/
pkg_vmq_bridge_fetch = git
pkg_vmq_bridge_repo = https://github.com/erlio/vmq_bridge
pkg_vmq_bridge_commit = master
PACKAGES += vmq_graphite
pkg_vmq_graphite_name = vmq_graphite
pkg_vmq_graphite_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_graphite_homepage = https://verne.mq/
pkg_vmq_graphite_fetch = git
pkg_vmq_graphite_repo = https://github.com/erlio/vmq_graphite
pkg_vmq_graphite_commit = master
PACKAGES += vmq_passwd
pkg_vmq_passwd_name = vmq_passwd
pkg_vmq_passwd_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_passwd_homepage = https://verne.mq/
pkg_vmq_passwd_fetch = git
pkg_vmq_passwd_repo = https://github.com/erlio/vmq_passwd
pkg_vmq_passwd_commit = master
PACKAGES += vmq_server
pkg_vmq_server_name = vmq_server
pkg_vmq_server_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_server_homepage = https://verne.mq/
pkg_vmq_server_fetch = git
pkg_vmq_server_repo = https://github.com/erlio/vmq_server
pkg_vmq_server_commit = master
PACKAGES += vmq_snmp
pkg_vmq_snmp_name = vmq_snmp
pkg_vmq_snmp_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_snmp_homepage = https://verne.mq/
pkg_vmq_snmp_fetch = git
pkg_vmq_snmp_repo = https://github.com/erlio/vmq_snmp
pkg_vmq_snmp_commit = master
PACKAGES += vmq_systree
pkg_vmq_systree_name = vmq_systree
pkg_vmq_systree_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_systree_homepage = https://verne.mq/
pkg_vmq_systree_fetch = git
pkg_vmq_systree_repo = https://github.com/erlio/vmq_systree
pkg_vmq_systree_commit = master
PACKAGES += vmstats
pkg_vmstats_name = vmstats
pkg_vmstats_description = tiny Erlang app that works in conjunction with statsderl in order to generate information on the Erlang VM for graphite logs.
pkg_vmstats_homepage = https://github.com/ferd/vmstats
pkg_vmstats_fetch = git
pkg_vmstats_repo = https://github.com/ferd/vmstats
pkg_vmstats_commit = master
PACKAGES += walrus
pkg_walrus_name = walrus
pkg_walrus_description = Walrus - Mustache-like Templating
pkg_walrus_homepage = https://github.com/devinus/walrus
pkg_walrus_fetch = git
pkg_walrus_repo = https://github.com/devinus/walrus
pkg_walrus_commit = master
PACKAGES += webmachine
pkg_webmachine_name = webmachine
pkg_webmachine_description = A REST-based system for building web applications.
pkg_webmachine_homepage = https://github.com/basho/webmachine
pkg_webmachine_fetch = git
pkg_webmachine_repo = https://github.com/basho/webmachine
pkg_webmachine_commit = master
PACKAGES += websocket_client
pkg_websocket_client_name = websocket_client
pkg_websocket_client_description = Erlang websocket client (ws and wss supported)
pkg_websocket_client_homepage = https://github.com/jeremyong/websocket_client
pkg_websocket_client_fetch = git
pkg_websocket_client_repo = https://github.com/jeremyong/websocket_client
pkg_websocket_client_commit = master
PACKAGES += worker_pool
pkg_worker_pool_name = worker_pool
pkg_worker_pool_description = a simple erlang worker pool
pkg_worker_pool_homepage = https://github.com/inaka/worker_pool
pkg_worker_pool_fetch = git
pkg_worker_pool_repo = https://github.com/inaka/worker_pool
pkg_worker_pool_commit = 1.0.2
PACKAGES += wrangler
pkg_wrangler_name = wrangler
pkg_wrangler_description = Import of the Wrangler svn repository.
pkg_wrangler_homepage = http://www.cs.kent.ac.uk/projects/wrangler/Home.html
pkg_wrangler_fetch = git
pkg_wrangler_repo = https://github.com/RefactoringTools/wrangler
pkg_wrangler_commit = master
PACKAGES += wsock
pkg_wsock_name = wsock
pkg_wsock_description = Erlang library to build WebSocket clients and servers
pkg_wsock_homepage = https://github.com/madtrick/wsock
pkg_wsock_fetch = git
pkg_wsock_repo = https://github.com/madtrick/wsock
pkg_wsock_commit = master
PACKAGES += xhttpc
pkg_xhttpc_name = xhttpc
pkg_xhttpc_description = Extensible HTTP Client for Erlang
pkg_xhttpc_homepage = https://github.com/seriyps/xhttpc
pkg_xhttpc_fetch = git
pkg_xhttpc_repo = https://github.com/seriyps/xhttpc
pkg_xhttpc_commit = master
PACKAGES += xref_runner
pkg_xref_runner_name = xref_runner
pkg_xref_runner_description = Erlang Xref Runner (inspired in rebar xref)
pkg_xref_runner_homepage = https://github.com/inaka/xref_runner
pkg_xref_runner_fetch = git
pkg_xref_runner_repo = https://github.com/inaka/xref_runner
pkg_xref_runner_commit = 0.2.0
PACKAGES += yamerl
pkg_yamerl_name = yamerl
pkg_yamerl_description = YAML 1.2 parser in pure Erlang
pkg_yamerl_homepage = https://github.com/yakaz/yamerl
pkg_yamerl_fetch = git
pkg_yamerl_repo = https://github.com/yakaz/yamerl
pkg_yamerl_commit = master
PACKAGES += yamler
pkg_yamler_name = yamler
pkg_yamler_description = libyaml-based yaml loader for Erlang
pkg_yamler_homepage = https://github.com/goertzenator/yamler
pkg_yamler_fetch = git
pkg_yamler_repo = https://github.com/goertzenator/yamler
pkg_yamler_commit = master
PACKAGES += yaws
pkg_yaws_name = yaws
pkg_yaws_description = Yaws webserver
pkg_yaws_homepage = http://yaws.hyber.org
pkg_yaws_fetch = git
pkg_yaws_repo = https://github.com/klacke/yaws
pkg_yaws_commit = master
PACKAGES += zab_engine
pkg_zab_engine_name = zab_engine
pkg_zab_engine_description = zab propotocol implement by erlang
pkg_zab_engine_homepage = https://github.com/xinmingyao/zab_engine
pkg_zab_engine_fetch = git
pkg_zab_engine_repo = https://github.com/xinmingyao/zab_engine
pkg_zab_engine_commit = master
PACKAGES += zeta
pkg_zeta_name = zeta
pkg_zeta_description = HTTP access log parser in Erlang
pkg_zeta_homepage = https://github.com/s1n4/zeta
pkg_zeta_fetch = git
pkg_zeta_repo = https://github.com/s1n4/zeta
pkg_zeta_commit =
PACKAGES += zippers
pkg_zippers_name = zippers
pkg_zippers_description = A library for functional zipper data structures in Erlang. Read more on zippers
pkg_zippers_homepage = https://github.com/ferd/zippers
pkg_zippers_fetch = git
pkg_zippers_repo = https://github.com/ferd/zippers
pkg_zippers_commit = master
PACKAGES += zlists
pkg_zlists_name = zlists
pkg_zlists_description = Erlang lazy lists library.
pkg_zlists_homepage = https://github.com/vjache/erlang-zlists
pkg_zlists_fetch = git
pkg_zlists_repo = https://github.com/vjache/erlang-zlists
pkg_zlists_commit = master
PACKAGES += zraft_lib
pkg_zraft_lib_name = zraft_lib
pkg_zraft_lib_description = Erlang raft consensus protocol implementation
pkg_zraft_lib_homepage = https://github.com/dreyk/zraft_lib
pkg_zraft_lib_fetch = git
pkg_zraft_lib_repo = https://github.com/dreyk/zraft_lib
pkg_zraft_lib_commit = master
PACKAGES += zucchini
pkg_zucchini_name = zucchini
pkg_zucchini_description = An Erlang INI parser
pkg_zucchini_homepage = https://github.com/devinus/zucchini
pkg_zucchini_fetch = git
pkg_zucchini_repo = https://github.com/devinus/zucchini
pkg_zucchini_commit = master
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: search
define pkg_print
$(verbose) printf "%s\n" \
$(if $(call core_eq,$(1),$(pkg_$(1)_name)),,"Pkg name: $(1)") \
"App name: $(pkg_$(1)_name)" \
"Description: $(pkg_$(1)_description)" \
"Home page: $(pkg_$(1)_homepage)" \
"Fetch with: $(pkg_$(1)_fetch)" \
"Repository: $(pkg_$(1)_repo)" \
"Commit: $(pkg_$(1)_commit)" \
""
endef
search:
ifdef q
$(foreach p,$(PACKAGES), \
$(if $(findstring $(call core_lc,$(q)),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \
$(call pkg_print,$(p))))
else
$(foreach p,$(PACKAGES),$(call pkg_print,$(p)))
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-deps distclean-pkg
# Configuration.
IGNORE_DEPS ?=
DEPS_DIR ?= $(CURDIR)/deps
export DEPS_DIR
REBAR_DEPS_DIR = $(DEPS_DIR)
export REBAR_DEPS_DIR
ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(filter-out $(IGNORE_DEPS),$(DEPS)))
ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),)
ifeq ($(ERL_LIBS),)
ERL_LIBS = $(DEPS_DIR)
else
ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)
endif
endif
export ERL_LIBS
# Verbosity.
dep_verbose_0 = @echo " DEP " $(1);
dep_verbose = $(dep_verbose_$(V))
# Core targets.
ifneq ($(SKIP_DEPS),)
deps::
else
deps:: $(ALL_DEPS_DIRS)
ifneq ($(IS_DEP),1)
$(verbose) rm -f $(ERLANG_MK_TMP)/deps.log
endif
$(verbose) mkdir -p $(ERLANG_MK_TMP)
$(verbose) for dep in $(ALL_DEPS_DIRS) ; do \
if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \
echo -n; \
else \
echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \
if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \
$(MAKE) -C $$dep IS_DEP=1 || exit $$?; \
else \
echo "ERROR: No Makefile to build dependency $$dep."; \
exit 1; \
fi \
fi \
done
endif
distclean:: distclean-deps distclean-pkg
# Deps related targets.
# @todo rename GNUmakefile and makefile into Makefile first, if they exist
# While Makefile file could be GNUmakefile or makefile,
# in practice only Makefile is needed so far.
define dep_autopatch
if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
if [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \
$(call dep_autopatch2,$(1)); \
elif [ 0 != `grep -ci rebar $(DEPS_DIR)/$(1)/Makefile` ]; then \
$(call dep_autopatch2,$(1)); \
elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk | xargs -r grep -i rebar`" ]; then \
$(call dep_autopatch2,$(1)); \
else \
if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \
$(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
$(call dep_autopatch_erlang_mk,$(1)); \
else \
$(call erlang,$(call dep_autopatch_app.erl,$(1))); \
fi \
fi \
else \
if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \
$(call dep_autopatch_noop,$(1)); \
else \
$(call dep_autopatch2,$(1)); \
fi \
fi
endef
define dep_autopatch2
$(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
if [ -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script ]; then \
$(call dep_autopatch_fetch_rebar); \
$(call dep_autopatch_rebar,$(1)); \
else \
$(call dep_autopatch_gen,$(1)); \
fi
endef
define dep_autopatch_noop
printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile
endef
# Overwrite erlang.mk with the current file by default.
ifeq ($(NO_AUTOPATCH_ERLANG_MK),)
define dep_autopatch_erlang_mk
echo "include $(ERLANG_MK_FILENAME)" > $(DEPS_DIR)/$(1)/erlang.mk
endef
else
define dep_autopatch_erlang_mk
echo -n
endef
endif
define dep_autopatch_gen
printf "%s\n" \
"ERLC_OPTS = +debug_info" \
"include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile
endef
define dep_autopatch_fetch_rebar
mkdir -p $(ERLANG_MK_TMP); \
if [ ! -d $(ERLANG_MK_TMP)/rebar ]; then \
git clone -q -n -- https://github.com/rebar/rebar $(ERLANG_MK_TMP)/rebar; \
cd $(ERLANG_MK_TMP)/rebar; \
git checkout -q 791db716b5a3a7671e0b351f95ddf24b848ee173; \
$(MAKE); \
cd -; \
fi
endef
define dep_autopatch_rebar
if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \
fi; \
$(call erlang,$(call dep_autopatch_rebar.erl,$(1))); \
rm -f $(DEPS_DIR)/$(1)/ebin/$(1).app
endef
define dep_autopatch_rebar.erl
application:set_env(rebar, log_level, debug),
Conf1 = case file:consult("$(DEPS_DIR)/$(1)/rebar.config") of
{ok, Conf0} -> Conf0;
_ -> []
end,
{Conf, OsEnv} = fun() ->
case filelib:is_file("$(DEPS_DIR)/$(1)/rebar.config.script") of
false -> {Conf1, []};
true ->
Bindings0 = erl_eval:new_bindings(),
Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0),
Bindings = erl_eval:add_binding('SCRIPT', "$(DEPS_DIR)/$(1)/rebar.config.script", Bindings1),
Before = os:getenv(),
{ok, Conf2} = file:script("$(DEPS_DIR)/$(1)/rebar.config.script", Bindings),
{Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)}
end
end(),
Write = fun (Text) ->
file:write_file("$(DEPS_DIR)/$(1)/Makefile", Text, [append])
end,
Escape = fun (Text) ->
re:replace(Text, "\\\\$$$$", "\$$$$$$$$", [global, {return, list}])
end,
Write("IGNORE_DEPS = edown eper eunit_formatters meck node_package "
"rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"),
Write("C_SRC_DIR = /path/do/not/exist\n"),
Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"),
Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]),
fun() ->
Write("ERLC_OPTS = +debug_info\nexport ERLC_OPTS\n"),
case lists:keyfind(erl_opts, 1, Conf) of
false -> ok;
{_, ErlOpts} ->
lists:foreach(fun
({d, D}) ->
Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
({i, I}) ->
Write(["ERLC_OPTS += -I ", I, "\n"]);
({platform_define, Regex, D}) ->
case rebar_utils:is_arch(Regex) of
true -> Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
false -> ok
end;
({parse_transform, PT}) ->
Write("ERLC_OPTS += +'{parse_transform, " ++ atom_to_list(PT) ++ "}'\n");
(_) -> ok
end, ErlOpts)
end,
Write("\n")
end(),
fun() ->
File = case lists:keyfind(deps, 1, Conf) of
false -> [];
{_, Deps} ->
[begin case case Dep of
{N, S} when is_atom(N), is_list(S) -> {N, {hex, S}};
{N, S} when is_tuple(S) -> {N, S};
{N, _, S} -> {N, S};
{N, _, S, _} -> {N, S};
_ -> false
end of
false -> ok;
{Name, Source} ->
{Method, Repo, Commit} = case Source of
{hex, V} -> {hex, undefined, V};
{git, R} -> {git, R, master};
{M, R, {branch, C}} -> {M, R, C};
{M, R, {ref, C}} -> {M, R, C};
{M, R, {tag, C}} -> {M, R, C};
{M, R, C} -> {M, R, C}
end,
Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit]))
end end || Dep <- Deps]
end
end(),
fun() ->
case lists:keyfind(erl_first_files, 1, Conf) of
false -> ok;
{_, Files} ->
Names = [[" ", case lists:reverse(F) of
"lre." ++ Elif -> lists:reverse(Elif);
Elif -> lists:reverse(Elif)
end] || "src/" ++ F <- Files],
Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names]))
end
end(),
FindFirst = fun(F, Fd) ->
case io:parse_erl_form(Fd, undefined) of
{ok, {attribute, _, compile, {parse_transform, PT}}, _} ->
[PT, F(F, Fd)];
{ok, {attribute, _, compile, CompileOpts}, _} when is_list(CompileOpts) ->
case proplists:get_value(parse_transform, CompileOpts) of
undefined -> [F(F, Fd)];
PT -> [PT, F(F, Fd)]
end;
{ok, {attribute, _, include, Hrl}, _} ->
case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ ->
case file:open("$(DEPS_DIR)/$(1)/src/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ -> [F(F, Fd)]
end
end;
{ok, {attribute, _, include_lib, "$(1)/include/" ++ Hrl}, _} ->
{ok, HrlFd} = file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]),
[F(F, HrlFd), F(F, Fd)];
{ok, {attribute, _, include_lib, Hrl}, _} ->
case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ -> [F(F, Fd)]
end;
{ok, {attribute, _, import, {Imp, _}}, _} ->
case file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(Imp) ++ ".erl", [read]) of
{ok, ImpFd} -> [Imp, F(F, ImpFd), F(F, Fd)];
_ -> [F(F, Fd)]
end;
{eof, _} ->
file:close(Fd),
[];
_ ->
F(F, Fd)
end
end,
fun() ->
ErlFiles = filelib:wildcard("$(DEPS_DIR)/$(1)/src/*.erl"),
First0 = lists:usort(lists:flatten([begin
{ok, Fd} = file:open(F, [read]),
FindFirst(FindFirst, Fd)
end || F <- ErlFiles])),
First = lists:flatten([begin
{ok, Fd} = file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", [read]),
FindFirst(FindFirst, Fd)
end || M <- First0, lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)]) ++ First0,
Write(["COMPILE_FIRST +=", [[" ", atom_to_list(M)] || M <- First,
lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)], "\n"])
end(),
Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app\n"),
Write("\npreprocess::\n"),
Write("\npre-deps::\n"),
Write("\npre-app::\n"),
PatchHook = fun(Cmd) ->
case Cmd of
"make -C" ++ Cmd1 -> "$$$$\(MAKE) -C" ++ Escape(Cmd1);
"gmake -C" ++ Cmd1 -> "$$$$\(MAKE) -C" ++ Escape(Cmd1);
"make " ++ Cmd1 -> "$$$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
"gmake " ++ Cmd1 -> "$$$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
_ -> Escape(Cmd)
end
end,
fun() ->
case lists:keyfind(pre_hooks, 1, Conf) of
false -> ok;
{_, Hooks} ->
[case H of
{'get-deps', Cmd} ->
Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n");
{compile, Cmd} ->
Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
{Regex, compile, Cmd} ->
case rebar_utils:is_arch(Regex) of
true -> Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
false -> ok
end;
_ -> ok
end || H <- Hooks]
end
end(),
ShellToMk = fun(V) ->
re:replace(re:replace(V, "(\\\\$$$$)(\\\\w*)", "\\\\1(\\\\2)", [global]),
"-Werror\\\\b", "", [{return, list}, global])
end,
PortSpecs = fun() ->
case lists:keyfind(port_specs, 1, Conf) of
false ->
case filelib:is_dir("$(DEPS_DIR)/$(1)/c_src") of
false -> [];
true ->
[{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"),
proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}]
end;
{_, Specs} ->
lists:flatten([case S of
{Output, Input} -> {ShellToMk(Output), Input, []};
{Regex, Output, Input} ->
case rebar_utils:is_arch(Regex) of
true -> {ShellToMk(Output), Input, []};
false -> []
end;
{Regex, Output, Input, [{env, Env}]} ->
case rebar_utils:is_arch(Regex) of
true -> {ShellToMk(Output), Input, Env};
false -> []
end
end || S <- Specs])
end
end(),
PortSpecWrite = fun (Text) ->
file:write_file("$(DEPS_DIR)/$(1)/c_src/Makefile.erlang.mk", Text, [append])
end,
case PortSpecs of
[] -> ok;
_ ->
Write("\npre-app::\n\t$$$$\(MAKE) -f c_src/Makefile.erlang.mk\n"),
PortSpecWrite(io_lib:format("ERL_CFLAGS = -finline-functions -Wall -fPIC -I ~s/erts-~s/include -I ~s\n",
[code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])),
PortSpecWrite(io_lib:format("ERL_LDFLAGS = -L ~s -lerl_interface -lei\n",
[code:lib_dir(erl_interface, lib)])),
[PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv],
FilterEnv = fun(Env) ->
lists:flatten([case E of
{_, _} -> E;
{Regex, K, V} ->
case rebar_utils:is_arch(Regex) of
true -> {K, V};
false -> []
end
end || E <- Env])
end,
MergeEnv = fun(Env) ->
lists:foldl(fun ({K, V}, Acc) ->
case lists:keyfind(K, 1, Acc) of
false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc];
{_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc]
end
end, [], Env)
end,
PortEnv = case lists:keyfind(port_env, 1, Conf) of
false -> [];
{_, PortEnv0} -> FilterEnv(PortEnv0)
end,
PortSpec = fun ({Output, Input0, Env}) ->
filelib:ensure_dir("$(DEPS_DIR)/$(1)/" ++ Output),
Input = [[" ", I] || I <- Input0],
PortSpecWrite([
[["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))],
case $(PLATFORM) of
darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress";
_ -> ""
end,
"\n\nall:: ", Output, "\n\n",
"%.o: %.c\n\t$$$$\(CC) -c -o $$$$\@ $$$$\< $$$$\(CFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.C\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.cc\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.cpp\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
[[Output, ": ", K, " = ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))],
Output, ": $$$$\(foreach ext,.c .C .cc .cpp,",
"$$$$\(patsubst %$$$$\(ext),%.o,$$$$\(filter %$$$$\(ext),$$$$\(wildcard", Input, "))))\n",
"\t$$$$\(CC) -o $$$$\@ $$$$\? $$$$\(LDFLAGS) $$$$\(ERL_LDFLAGS) $$$$\(DRV_LDFLAGS) $$$$\(EXE_LDFLAGS)",
case filename:extension(Output) of
[] -> "\n";
_ -> " -shared\n"
end])
end,
[PortSpec(S) || S <- PortSpecs]
end,
Write("\ninclude $(ERLANG_MK_FILENAME)"),
RunPlugin = fun(Plugin, Step) ->
case erlang:function_exported(Plugin, Step, 2) of
false -> ok;
true ->
c:cd("$(DEPS_DIR)/$(1)/"),
Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(),
dict:store(base_dir, "", dict:new())}, undefined),
io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret])
end
end,
fun() ->
case lists:keyfind(plugins, 1, Conf) of
false -> ok;
{_, Plugins} ->
[begin
case lists:keyfind(deps, 1, Conf) of
false -> ok;
{_, Deps} ->
case lists:keyfind(P, 1, Deps) of
false -> ok;
_ ->
Path = "$(DEPS_DIR)/" ++ atom_to_list(P),
io:format("~s", [os:cmd("$(MAKE) -C $(DEPS_DIR)/$(1) " ++ Path)]),
io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]),
code:add_patha(Path ++ "/ebin")
end
end
end || P <- Plugins],
[case code:load_file(P) of
{module, P} -> ok;
_ ->
case lists:keyfind(plugin_dir, 1, Conf) of
false -> ok;
{_, PluginsDir} ->
ErlFile = "$(DEPS_DIR)/$(1)/" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl",
{ok, P, Bin} = compile:file(ErlFile, [binary]),
{module, P} = code:load_binary(P, ErlFile, Bin)
end
end || P <- Plugins],
[RunPlugin(P, preprocess) || P <- Plugins],
[RunPlugin(P, pre_compile) || P <- Plugins]
end
end(),
halt()
endef
define dep_autopatch_app.erl
UpdateModules = fun(App) ->
case filelib:is_regular(App) of
false -> ok;
true ->
{ok, [{application, $(1), L0}]} = file:consult(App),
Mods = filelib:fold_files("$(DEPS_DIR)/$(1)/src", "\\\\.erl$$$$", true,
fun (F, Acc) -> [list_to_atom(filename:rootname(filename:basename(F)))|Acc] end, []),
L = lists:keystore(modules, 1, L0, {modules, Mods}),
ok = file:write_file(App, io_lib:format("~p.~n", [{application, $(1), L}]))
end
end,
UpdateModules("$(DEPS_DIR)/$(1)/ebin/$(1).app"),
halt()
endef
define dep_autopatch_appsrc.erl
AppSrcOut = "$(DEPS_DIR)/$(1)/src/$(1).app.src",
AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(DEPS_DIR)/$(1)/ebin/$(1).app"; true -> AppSrcOut end,
case filelib:is_regular(AppSrcIn) of
false -> ok;
true ->
{ok, [{application, $(1), L0}]} = file:consult(AppSrcIn),
L1 = lists:keystore(modules, 1, L0, {modules, []}),
L2 = case lists:keyfind(vsn, 1, L1) of {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, "git"}); _ -> L1 end,
L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end,
ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L3}])),
case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end
end,
halt()
endef
define hex_fetch.erl
ssl:start(),
inets:start(),
{ok, {{_, 200, _}, _, Body}} = httpc:request(get,
{"https://s3.amazonaws.com/s3.hex.pm/tarballs/$(1)-$(2).tar", []},
[], [{body_format, binary}]),
{ok, Files} = erl_tar:extract({binary, Body}, [memory]),
{_, Source} = lists:keyfind("contents.tar.gz", 1, Files),
ok = erl_tar:extract({binary, Source}, [{cwd, "$(DEPS_DIR)/$(1)"}, compressed]),
halt()
endef
define dep_fetch
if [ "$(2)" = "git" ]; then \
git clone -q -n -- $(3) $(DEPS_DIR)/$(1); \
cd $(DEPS_DIR)/$(1) && git checkout -q $(4); \
elif [ "$(2)" = "hg" ]; then \
hg clone -q -U $(3) $(DEPS_DIR)/$(1); \
cd $(DEPS_DIR)/$(1) && hg update -q $(4); \
elif [ "$(2)" = "svn" ]; then \
svn checkout -q $(3) $(DEPS_DIR)/$(1); \
elif [ "$(2)" = "cp" ]; then \
cp -R $(3) $(DEPS_DIR)/$(1); \
elif [ "$(2)" = "hex" ]; then \
$(call erlang,$(call hex_fetch.erl,$(1),$(strip $(4)))); \
else \
echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \
exit 78; \
fi
endef
define dep_target
$(DEPS_DIR)/$(1):
$(verbose) mkdir -p $(DEPS_DIR)
ifeq (,$(dep_$(1)))
$(dep_verbose) $(call dep_fetch,$(pkg_$(1)_name),$(pkg_$(1)_fetch), \
$(patsubst git://github.com/%,https://github.com/%,$(pkg_$(1)_repo)), \
$(pkg_$(1)_commit))
else
ifeq (1,$(words $(dep_$(1))))
$(dep_verbose) $(call dep_fetch,$(1),git, \
$(patsubst git://github.com/%,https://github.com/%,$(dep_$(1))), \
master)
else
ifeq (2,$(words $(dep_$(1))))
$(dep_verbose) $(call dep_fetch,$(1),git, \
$(patsubst git://github.com/%,https://github.com/%,$(word 1,$(dep_$(1)))), \
$(word 2,$(dep_$(1))))
else
$(dep_verbose) $(call dep_fetch,$(1),$(word 1,$(dep_$(1))), \
$(patsubst git://github.com/%,https://github.com/%,$(word 2,$(dep_$(1)))), \
$(word 3,$(dep_$(1))))
endif
endif
endif
$(verbose) if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ]; then \
echo " AUTO " $(1); \
cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \
fi
- $(verbose) if [ -f $(DEPS_DIR)/$(1)/configure ]; then \
echo " CONF " $(1); \
cd $(DEPS_DIR)/$(1) && ./configure; \
fi
ifeq ($(filter $(1),$(NO_AUTOPATCH)),)
$(verbose) if [ "$(1)" = "amqp_client" -a "$(RABBITMQ_CLIENT_PATCH)" ]; then \
if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
echo " PATCH Downloading rabbitmq-codegen"; \
git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
fi; \
if [ ! -d $(DEPS_DIR)/rabbitmq-server ]; then \
echo " PATCH Downloading rabbitmq-server"; \
git clone https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server; \
fi; \
ln -s $(DEPS_DIR)/amqp_client/deps/rabbit_common-0.0.0 $(DEPS_DIR)/rabbit_common; \
elif [ "$(1)" = "rabbit" -a "$(RABBITMQ_SERVER_PATCH)" ]; then \
if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
echo " PATCH Downloading rabbitmq-codegen"; \
git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
fi \
else \
$(call dep_autopatch,$(1)) \
fi
endif
endef
$(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep))))
distclean-deps:
$(gen_verbose) rm -rf $(DEPS_DIR)
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
# Verbosity.
proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F));
proto_verbose = $(proto_verbose_$(V))
# Core targets.
define compile_proto
$(verbose) mkdir -p ebin/ include/
$(proto_verbose) $(call erlang,$(call compile_proto.erl,$(1)))
$(proto_verbose) erlc +debug_info -o ebin/ ebin/*.erl
$(verbose) rm ebin/*.erl
endef
define compile_proto.erl
[begin
Dir = filename:dirname(filename:dirname(F)),
protobuffs_compile:generate_source(F,
[{output_include_dir, Dir ++ "/include"},
{output_src_dir, Dir ++ "/ebin"}])
end || F <- string:tokens("$(1)", " ")],
halt().
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.proto))
$(if $(strip $?),$(call compile_proto,$?))
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: clean-app
# Configuration.
ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
+warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
COMPILE_FIRST ?=
COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
ERLC_EXCLUDE ?=
ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
ERLC_MIB_OPTS ?=
COMPILE_MIB_FIRST ?=
COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
# Verbosity.
app_verbose_0 = @echo " APP " $(PROJECT);
app_verbose = $(app_verbose_$(V))
appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
appsrc_verbose = $(appsrc_verbose_$(V))
erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
$(filter %.erl %.core,$(?F)));
erlc_verbose = $(erlc_verbose_$(V))
xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
xyrl_verbose = $(xyrl_verbose_$(V))
asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F));
asn1_verbose = $(asn1_verbose_$(V))
mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
mib_verbose = $(mib_verbose_$(V))
# Targets.
ifeq ($(wildcard ebin/test),)
app:: app-build
else
app:: clean app-build
endif
ifeq ($(wildcard src/$(PROJECT)_app.erl),)
define app_file
{application, $(PROJECT), [
{description, "$(PROJECT_DESCRIPTION)"},
{vsn, "$(PROJECT_VERSION)"},
{id, "$(1)"},
{modules, [$(call comma_list,$(2))]},
{registered, []},
{applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(DEPS))]}
]}.
endef
else
define app_file
{application, $(PROJECT), [
{description, "$(PROJECT_DESCRIPTION)"},
{vsn, "$(PROJECT_VERSION)"},
{id, "$(1)"},
{modules, [$(call comma_list,$(2))]},
{registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]},
{applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(DEPS))]},
{mod, {$(PROJECT)_app, []}}
]}.
endef
endif
app-build: erlc-include ebin/$(PROJECT).app
$(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true))
$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam))))))
ifeq ($(wildcard src/$(PROJECT).app.src),)
$(app_verbose) echo $(subst $(newline),,$(subst ",\",$(call app_file,$(GITDESCRIBE),$(MODULES)))) \
> ebin/$(PROJECT).app
else
$(verbose) if [ -z "$$(grep -E '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \
echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \
exit 1; \
fi
$(appsrc_verbose) cat src/$(PROJECT).app.src \
| sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \
| sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(GITDESCRIBE)\"}/" \
> ebin/$(PROJECT).app
endif
erlc-include:
- $(verbose) if [ -d ebin/ ]; then \
find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \
fi
define compile_erl
$(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \
-pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),\
$(COMPILE_FIRST_PATHS) $(1))
endef
define compile_xyrl
$(xyrl_verbose) erlc -v -o ebin/ $(1)
$(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl
$(verbose) rm ebin/*.erl
endef
define compile_asn1
$(asn1_verbose) erlc -v -I include/ -o ebin/ $(1)
$(verbose) mv ebin/*.hrl include/
$(verbose) mv ebin/*.asn1db include/
$(verbose) rm ebin/*.erl
endef
define compile_mib
$(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ \
-I priv/mibs/ $(COMPILE_MIB_FIRST_PATHS) $(1)
$(mib_verbose) erlc -o include/ -- priv/mibs/*.bin
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app::
$(verbose) mkdir -p ebin/
ifneq ($(wildcard asn1/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,asn1/,*.asn1))
$(verbose) mkdir -p include
$(if $(strip $?),$(call compile_asn1,$?))
endif
ifneq ($(wildcard mibs/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,mibs/,*.mib))
$(verbose) mkdir -p priv/mibs/ include
$(if $(strip $?),$(call compile_mib,$?))
endif
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.erl *.core))
$(if $(strip $?),$(call compile_erl,$?))
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.xrl *.yrl))
$(if $(strip $?),$(call compile_xyrl,$?))
endif
clean:: clean-app
clean-app:
$(gen_verbose) rm -rf ebin/ priv/mibs/ \
$(addprefix include/,$(addsuffix .hrl,$(notdir $(basename $(call core_find,mibs/,*.mib)))))
# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: docs-deps
# Configuration.
ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))
# Targets.
$(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))
ifneq ($(SKIP_DEPS),)
doc-deps:
else
doc-deps: $(ALL_DOC_DEPS_DIRS)
$(verbose) for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: test-deps test-dir test-build clean-test-dir
# Configuration.
TEST_DIR ?= $(CURDIR)/test
ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
TEST_ERLC_OPTS += -DTEST=1
# Targets.
$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
ifneq ($(SKIP_DEPS),)
test-deps:
else
test-deps: $(ALL_TEST_DEPS_DIRS)
$(verbose) for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done
endif
ifneq ($(wildcard $(TEST_DIR)),)
test-dir:
$(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o $(TEST_DIR) \
$(call core_find,$(TEST_DIR)/,*.erl) -pa ebin/
endif
ifeq ($(wildcard ebin/test),)
test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build:: clean deps test-deps
$(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
$(gen_verbose) touch ebin/test
else
test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build:: deps test-deps
$(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
endif
clean:: clean-test-dir
clean-test-dir:
ifneq ($(wildcard $(TEST_DIR)/*.beam),)
$(gen_verbose) rm -f $(TEST_DIR)/*.beam
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc
MAN_INSTALL_PATH ?= /usr/local/share/man
MAN_SECTIONS ?= 3 7
docs:: asciidoc
asciidoc: distclean-asciidoc doc-deps asciidoc-guide asciidoc-manual
ifeq ($(wildcard doc/src/guide/book.asciidoc),)
asciidoc-guide:
else
asciidoc-guide:
a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf
a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/
endif
ifeq ($(wildcard doc/src/manual/*.asciidoc),)
asciidoc-manual:
else
asciidoc-manual:
for f in doc/src/manual/*.asciidoc ; do \
a2x -v -f manpage $$f ; \
done
for s in $(MAN_SECTIONS); do \
mkdir -p doc/man$$s/ ; \
mv doc/src/manual/*.$$s doc/man$$s/ ; \
gzip doc/man$$s/*.$$s ; \
done
install-docs:: install-asciidoc
install-asciidoc: asciidoc-manual
for s in $(MAN_SECTIONS); do \
mkdir -p $(MAN_INSTALL_PATH)/man$$s/ ; \
install -g 0 -o 0 -m 0644 doc/man$$s/*.gz $(MAN_INSTALL_PATH)/man$$s/ ; \
done
endif
distclean:: distclean-asciidoc
distclean-asciidoc:
$(gen_verbose) rm -rf doc/html/ doc/guide.pdf doc/man3/ doc/man7/
# Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Bootstrap targets:" \
" bootstrap Generate a skeleton of an OTP application" \
" bootstrap-lib Generate a skeleton of an OTP library" \
" bootstrap-rel Generate the files needed to build a release" \
" new t=TPL n=NAME Generate a module NAME based on the template TPL" \
" list-templates List available templates"
# Bootstrap templates.
define bs_appsrc
{application, $(PROJECT), [
{description, ""},
{vsn, "0.1.0"},
{id, "git"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, {$(PROJECT)_app, []}},
{env, []}
]}.
endef
define bs_appsrc_lib
{application, $(PROJECT), [
{description, ""},
{vsn, "0.1.0"},
{id, "git"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]}
]}.
endef
ifdef SP
define bs_Makefile
PROJECT = $(PROJECT)
# Whitespace to be used when creating files from templates.
SP = $(SP)
include erlang.mk
endef
else
define bs_Makefile
PROJECT = $(PROJECT)
include erlang.mk
endef
endif
define bs_app
-module($(PROJECT)_app).
-behaviour(application).
-export([start/2]).
-export([stop/1]).
start(_Type, _Args) ->
$(PROJECT)_sup:start_link().
stop(_State) ->
ok.
endef
define bs_relx_config
{release, {$(PROJECT)_release, "1"}, [$(PROJECT)]}.
{extended_start_script, true}.
{sys_config, "rel/sys.config"}.
{vm_args, "rel/vm.args"}.
endef
define bs_sys_config
[
].
endef
define bs_vm_args
-name $(PROJECT)@127.0.0.1
-setcookie $(PROJECT)
-heart
endef
# Normal templates.
define tpl_supervisor
-module($(n)).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Procs = [],
{ok, {{one_for_one, 1, 5}, Procs}}.
endef
define tpl_gen_server
-module($(n)).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link(?MODULE, [], []).
%% gen_server.
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
endef
define tpl_cowboy_http
-module($(n)).
-behaviour(cowboy_http_handler).
-export([init/3]).
-export([handle/2]).
-export([terminate/3]).
-record(state, {
}).
init(_, Req, _Opts) ->
{ok, Req, #state{}}.
handle(Req, State=#state{}) ->
{ok, Req2} = cowboy_req:reply(200, Req),
{ok, Req2, State}.
terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_gen_fsm
-module($(n)).
-behaviour(gen_fsm).
%% API.
-export([start_link/0]).
%% gen_fsm.
-export([init/1]).
-export([state_name/2]).
-export([handle_event/3]).
-export([state_name/3]).
-export([handle_sync_event/4]).
-export([handle_info/3]).
-export([terminate/3]).
-export([code_change/4]).
-record(state, {
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_fsm:start_link(?MODULE, [], []).
%% gen_fsm.
init([]) ->
{ok, state_name, #state{}}.
state_name(_Event, StateData) ->
{next_state, state_name, StateData}.
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
state_name(_Event, _From, StateData) ->
{reply, ignored, state_name, StateData}.
handle_sync_event(_Event, _From, StateName, StateData) ->
{reply, ignored, StateName, StateData}.
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
terminate(_Reason, _StateName, _StateData) ->
ok.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
endef
define tpl_cowboy_loop
-module($(n)).
-behaviour(cowboy_loop_handler).
-export([init/3]).
-export([info/3]).
-export([terminate/3]).
-record(state, {
}).
init(_, Req, _Opts) ->
{loop, Req, #state{}, 5000, hibernate}.
info(_Info, Req, State) ->
{loop, Req, State, hibernate}.
terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_cowboy_rest
-module($(n)).
-export([init/3]).
-export([content_types_provided/2]).
-export([get_html/2]).
init(_, _Req, _Opts) ->
{upgrade, protocol, cowboy_rest}.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}.
get_html(Req, State) ->
{<<"<html><body>This is REST!</body></html>">>, Req, State}.
endef
define tpl_cowboy_ws
-module($(n)).
-behaviour(cowboy_websocket_handler).
-export([init/3]).
-export([websocket_init/3]).
-export([websocket_handle/3]).
-export([websocket_info/3]).
-export([websocket_terminate/3]).
-record(state, {
}).
init(_, _, _) ->
{upgrade, protocol, cowboy_websocket}.
websocket_init(_, Req, _Opts) ->
Req2 = cowboy_req:compact(Req),
{ok, Req2, #state{}}.
websocket_handle({text, Data}, Req, State) ->
{reply, {text, Data}, Req, State};
websocket_handle({binary, Data}, Req, State) ->
{reply, {binary, Data}, Req, State};
websocket_handle(_Frame, Req, State) ->
{ok, Req, State}.
websocket_info(_Info, Req, State) ->
{ok, Req, State}.
websocket_terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_ranch_protocol
-module($(n)).
-behaviour(ranch_protocol).
-export([start_link/4]).
-export([init/4]).
-type opts() :: [].
-export_type([opts/0]).
-record(state, {
socket :: inet:socket(),
transport :: module()
}).
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.
-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, _Opts) ->
ok = ranch:accept_ack(Ref),
loop(#state{socket=Socket, transport=Transport}).
loop(State) ->
loop(State).
endef
# Plugin-specific targets.
define render_template
$(verbose) echo "$${_$(1)}" > $(2)
endef
ifndef WS
ifdef SP
WS = $(subst a,,a $(wordlist 1,$(SP),a a a a a a a a a a a a a a a a a a a a))
else
WS = $(tab)
endif
endif
$(foreach template,$(filter bs_% tpl_%,$(.VARIABLES)), \
$(eval _$(template) = $$(subst $$(tab),$$(WS),$$($(template)))) \
$(eval export _$(template)))
bootstrap:
ifneq ($(wildcard src/),)
$(error Error: src/ directory already exists)
endif
$(call render_template,bs_Makefile,Makefile)
$(verbose) mkdir src/
$(call render_template,bs_appsrc,src/$(PROJECT).app.src)
$(call render_template,bs_app,src/$(PROJECT)_app.erl)
$(eval n := $(PROJECT)_sup)
$(call render_template,tpl_supervisor,src/$(PROJECT)_sup.erl)
bootstrap-lib:
ifneq ($(wildcard src/),)
$(error Error: src/ directory already exists)
endif
$(call render_template,bs_Makefile,Makefile)
$(verbose) mkdir src/
$(call render_template,bs_appsrc_lib,src/$(PROJECT).app.src)
bootstrap-rel:
ifneq ($(wildcard relx.config),)
$(error Error: relx.config already exists)
endif
ifneq ($(wildcard rel/),)
$(error Error: rel/ directory already exists)
endif
$(call render_template,bs_relx_config,relx.config)
$(verbose) mkdir rel/
$(call render_template,bs_sys_config,rel/sys.config)
$(call render_template,bs_vm_args,rel/vm.args)
new:
ifeq ($(wildcard src/),)
$(error Error: src/ directory does not exist)
endif
ifndef t
$(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
endif
ifndef tpl_$(t)
$(error Unknown template)
endif
ifndef n
$(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
endif
$(call render_template,tpl_$(t),src/$(n).erl)
list-templates:
$(verbose) echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))
# Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: clean-c_src distclean-c_src-env
# Configuration.
C_SRC_DIR ?= $(CURDIR)/c_src
C_SRC_ENV ?= $(C_SRC_DIR)/env.mk
C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT).so
C_SRC_TYPE ?= shared
# System type and C compiler/flags.
ifeq ($(PLATFORM),darwin)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall
LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress
else ifeq ($(PLATFORM),freebsd)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
else ifeq ($(PLATFORM),linux)
CC ?= gcc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
endif
CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei
ifeq ($(C_SRC_TYPE),shared)
LDFLAGS += -shared
endif
# Verbosity.
c_verbose_0 = @echo " C " $(?F);
c_verbose = $(c_verbose_$(V))
cpp_verbose_0 = @echo " CPP " $(?F);
cpp_verbose = $(cpp_verbose_$(V))
link_verbose_0 = @echo " LD " $(@F);
link_verbose = $(link_verbose_$(V))
# Targets.
ifeq ($(wildcard $(C_SRC_DIR)),)
else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),)
app:: app-c_src
test-build:: app-c_src
app-c_src:
$(MAKE) -C $(C_SRC_DIR)
clean::
$(MAKE) -C $(C_SRC_DIR) clean
else
ifeq ($(SOURCES),)
SOURCES := $(sort $(call core_find,$(C_SRC_DIR)/,*.c *.C *.cc *.cpp))
endif
OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
app:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
$(C_SRC_OUTPUT): $(OBJECTS)
$(verbose) mkdir -p priv/
$(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT)
%.o: %.c
$(COMPILE_C) $(OUTPUT_OPTION) $<
%.o: %.cc
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.C
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.cpp
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
clean:: clean-c_src
clean-c_src:
$(gen_verbose) rm -f $(C_SRC_OUTPUT) $(OBJECTS)
endif
ifneq ($(wildcard $(C_SRC_DIR)),)
$(C_SRC_ENV):
$(verbose) $(ERL) -eval "file:write_file(\"$(C_SRC_ENV)\", \
io_lib:format( \
\"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \
\"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \
\"ERL_INTERFACE_LIB_DIR ?= ~s~n\", \
[code:root_dir(), erlang:system_info(version), \
code:lib_dir(erl_interface, include), \
code:lib_dir(erl_interface, lib)])), \
halt()."
distclean:: distclean-c_src-env
distclean-c_src-env:
$(gen_verbose) rm -f $(C_SRC_ENV)
-include $(C_SRC_ENV)
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: ci ci-setup distclean-kerl
KERL ?= $(CURDIR)/kerl
export KERL
KERL_URL ?= https://raw.githubusercontent.com/yrashk/kerl/master/kerl
OTP_GIT ?= https://github.com/erlang/otp
CI_INSTALL_DIR ?= $(HOME)/erlang
CI_OTP ?=
ifeq ($(strip $(CI_OTP)),)
ci::
else
ci:: $(addprefix ci-,$(CI_OTP))
ci-prepare: $(addprefix $(CI_INSTALL_DIR)/,$(CI_OTP))
ci-setup::
ci_verbose_0 = @echo " CI " $(1);
ci_verbose = $(ci_verbose_$(V))
define ci_target
ci-$(1): $(CI_INSTALL_DIR)/$(1)
$(ci_verbose) \
PATH="$(CI_INSTALL_DIR)/$(1)/bin:$(PATH)" \
CI_OTP_RELEASE="$(1)" \
CT_OPTS="-label $(1)" \
$(MAKE) clean ci-setup tests
endef
$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp))))
define ci_otp_target
ifeq ($(wildcard $(CI_INSTALL_DIR)/$(1)),)
$(CI_INSTALL_DIR)/$(1): $(KERL)
$(KERL) build git $(OTP_GIT) $(1) $(1)
$(KERL) install $(1) $(CI_INSTALL_DIR)/$(1)
endif
endef
$(foreach otp,$(CI_OTP),$(eval $(call ci_otp_target,$(otp))))
$(KERL):
$(gen_verbose) $(call core_http_get,$(KERL),$(KERL_URL))
$(verbose) chmod +x $(KERL)
help::
$(verbose) printf "%s\n" "" \
"Continuous Integration targets:" \
" ci Run '$(MAKE) tests' on all configured Erlang versions." \
"" \
"The CI_OTP variable must be defined with the Erlang versions" \
"that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3"
distclean:: distclean-kerl
distclean-kerl:
$(gen_verbose) rm -rf $(KERL)
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: ct distclean-ct
# Configuration.
CT_OPTS ?=
ifneq ($(wildcard $(TEST_DIR)),)
CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl))))
else
CT_SUITES ?=
endif
# Core targets.
tests:: ct
distclean:: distclean-ct
help::
$(verbose) printf "%s\n" "" \
"Common_test targets:" \
" ct Run all the common_test suites for this project" \
"" \
"All your common_test suites have their associated targets." \
"A suite named http_SUITE can be ran using the ct-http target."
# Plugin-specific targets.
CT_RUN = ct_run \
-no_auto_compile \
-noinput \
-pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin $(TEST_DIR) \
-dir $(TEST_DIR) \
-logdir $(CURDIR)/logs
ifeq ($(CT_SUITES),)
ct:
else
ct: test-build
$(verbose) mkdir -p $(CURDIR)/logs/
$(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS)
endif
define ct_suite_target
ct-$(1): test-build
$(verbose) mkdir -p $(CURDIR)/logs/
$(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS)
endef
$(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test))))
distclean-ct:
$(gen_verbose) rm -rf $(CURDIR)/logs/
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: plt distclean-plt dialyze
# Configuration.
DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt
export DIALYZER_PLT
PLT_APPS ?=
DIALYZER_DIRS ?= --src -r src
DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \
-Wunmatched_returns # -Wunderspecs
# Core targets.
check:: dialyze
distclean:: distclean-plt
help::
$(verbose) printf "%s\n" "" \
"Dialyzer targets:" \
" plt Build a PLT file for this project" \
" dialyze Analyze the project using Dialyzer"
# Plugin-specific targets.
$(DIALYZER_PLT): deps app
$(verbose) dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(ALL_DEPS_DIRS)
plt: $(DIALYZER_PLT)
distclean-plt:
$(gen_verbose) rm -f $(DIALYZER_PLT)
ifneq ($(wildcard $(DIALYZER_PLT)),)
dialyze:
else
dialyze: $(DIALYZER_PLT)
endif
$(verbose) dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-edoc edoc
# Configuration.
EDOC_OPTS ?=
# Core targets.
docs:: distclean-edoc edoc
distclean:: distclean-edoc
# Plugin-specific targets.
edoc: doc-deps
$(gen_verbose) $(ERL) -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), halt().'
distclean-edoc:
$(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
# Copyright (c) 2015, Erlang Solutions Ltd.
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: elvis distclean-elvis
# Configuration.
ELVIS_CONFIG ?= $(CURDIR)/elvis.config
ELVIS ?= $(CURDIR)/elvis
export ELVIS
ELVIS_URL ?= https://github.com/inaka/elvis/releases/download/0.2.5-beta2/elvis
ELVIS_CONFIG_URL ?= https://github.com/inaka/elvis/releases/download/0.2.5-beta2/elvis.config
ELVIS_OPTS ?=
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Elvis targets:" \
" elvis Run Elvis using the local elvis.config or download the default otherwise"
distclean:: distclean-elvis
# Plugin-specific targets.
$(ELVIS):
$(gen_verbose) $(call core_http_get,$(ELVIS),$(ELVIS_URL))
$(verbose) chmod +x $(ELVIS)
$(ELVIS_CONFIG):
$(verbose) $(call core_http_get,$(ELVIS_CONFIG),$(ELVIS_CONFIG_URL))
elvis: $(ELVIS) $(ELVIS_CONFIG)
$(verbose) $(ELVIS) rock -c $(ELVIS_CONFIG) $(ELVIS_OPTS)
distclean-elvis:
$(gen_verbose) rm -rf $(ELVIS)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
# Configuration.
DTL_FULL_PATH ?= 0
# Verbosity.
dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
dtl_verbose = $(dtl_verbose_$(V))
# Core targets.
define compile_erlydtl
$(dtl_verbose) $(ERL) -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \
Compile = fun(F) -> \
S = fun (1) -> re:replace(filename:rootname(string:sub_string(F, 11), ".dtl"), "/", "_", [{return, list}, global]); \
(0) -> filename:basename(F, ".dtl") \
end, \
Module = list_to_atom(string:to_lower(S($(DTL_FULL_PATH))) ++ "_dtl"), \
{ok, _} = erlydtl:compile(F, Module, [{out_dir, "ebin/"}, return_errors, {doc_root, "templates"}]) \
end, \
_ = [Compile(F) || F <- string:tokens("$(1)", " ")], \
halt().'
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,templates/,*.dtl))
$(if $(strip $?),$(call compile_erlydtl,$?))
endif
# Copyright (c) 2014 Dave Cottlehuber <dch@skunkwerks.at>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-escript escript
# Configuration.
ESCRIPT_NAME ?= $(PROJECT)
ESCRIPT_COMMENT ?= This is an -*- erlang -*- file
ESCRIPT_BEAMS ?= "ebin/*", "deps/*/ebin/*"
ESCRIPT_SYS_CONFIG ?= "rel/sys.config"
ESCRIPT_EMU_ARGS ?= -pa . \
-sasl errlog_type error \
-escript main $(ESCRIPT_NAME)
ESCRIPT_SHEBANG ?= /usr/bin/env escript
ESCRIPT_STATIC ?= "deps/*/priv/**", "priv/**"
# Core targets.
distclean:: distclean-escript
help::
$(verbose) printf "%s\n" "" \
"Escript targets:" \
" escript Build an executable escript archive" \
# Plugin-specific targets.
# Based on https://github.com/synrc/mad/blob/master/src/mad_bundle.erl
# Copyright (c) 2013 Maxim Sokhatsky, Synrc Research Center
# Modified MIT License, https://github.com/synrc/mad/blob/master/LICENSE :
# Software may only be used for the great good and the true happiness of all
# sentient beings.
define ESCRIPT_RAW
'Read = fun(F) -> {ok, B} = file:read_file(filename:absname(F)), B end,'\
'Files = fun(L) -> A = lists:concat([filelib:wildcard(X)||X<- L ]),'\
' [F || F <- A, not filelib:is_dir(F) ] end,'\
'Squash = fun(L) -> [{filename:basename(F), Read(F) } || F <- L ] end,'\
'Zip = fun(A, L) -> {ok,{_,Z}} = zip:create(A, L, [{compress,all},memory]), Z end,'\
'Ez = fun(Escript) ->'\
' Static = Files([$(ESCRIPT_STATIC)]),'\
' Beams = Squash(Files([$(ESCRIPT_BEAMS), $(ESCRIPT_SYS_CONFIG)])),'\
' Archive = Beams ++ [{ "static.gz", Zip("static.gz", Static)}],'\
' escript:create(Escript, [ $(ESCRIPT_OPTIONS)'\
' {archive, Archive, [memory]},'\
' {shebang, "$(ESCRIPT_SHEBANG)"},'\
' {comment, "$(ESCRIPT_COMMENT)"},'\
' {emu_args, " $(ESCRIPT_EMU_ARGS)"}'\
' ]),'\
' file:change_mode(Escript, 8#755)'\
'end,'\
'Ez("$(ESCRIPT_NAME)"),'\
'halt().'
endef
ESCRIPT_COMMAND = $(subst ' ',,$(ESCRIPT_RAW))
escript:: distclean-escript deps app
$(gen_verbose) $(ERL) -eval $(ESCRIPT_COMMAND)
distclean-escript:
$(gen_verbose) rm -f $(ESCRIPT_NAME)
# Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is contributed to erlang.mk and subject to the terms of the ISC License.
.PHONY: eunit
# Configuration
EUNIT_OPTS ?=
# Core targets.
tests:: eunit
help::
$(verbose) printf "%s\n" "" \
"EUnit targets:" \
" eunit Run all the EUnit tests for this project"
# Plugin-specific targets.
define eunit.erl
case "$(COVER)" of
"" -> ok;
_ ->
case cover:compile_beam_directory("ebin") of
{error, _} -> halt(1);
_ -> ok
end
end,
case eunit:test([$(call comma_list,$(1))], [$(EUNIT_OPTS)]) of
ok -> ok;
error -> halt(2)
end,
case "$(COVER)" of
"" -> ok;
_ ->
cover:export("eunit.coverdata")
end,
halt()
endef
EUNIT_EBIN_MODS = $(notdir $(basename $(call core_find,ebin/,*.beam)))
EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.beam)))
EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \
$(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),{module,'$(mod)'})
eunit: test-build
$(gen_verbose) $(ERL) -pa $(TEST_DIR) $(DEPS_DIR)/*/ebin ebin \
-eval "$(subst $(newline),,$(subst ",\",$(call eunit.erl,$(EUNIT_MODS))))"
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: relx-rel distclean-relx-rel distclean-relx run
# Configuration.
RELX_CONFIG ?= $(CURDIR)/relx.config
RELX ?= $(CURDIR)/relx
export RELX
RELX_URL ?= https://github.com/erlware/relx/releases/download/v2.0.0/relx
RELX_OPTS ?=
RELX_OUTPUT_DIR ?= _rel
ifeq ($(firstword $(RELX_OPTS)),-o)
RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS))
else
RELX_OPTS += -o $(RELX_OUTPUT_DIR)
endif
# Core targets.
ifeq ($(IS_DEP),)
ifneq ($(wildcard $(RELX_CONFIG)),)
rel:: distclean-relx-rel relx-rel
endif
endif
distclean:: distclean-relx-rel distclean-relx
# Plugin-specific targets.
$(RELX):
$(gen_verbose) $(call core_http_get,$(RELX),$(RELX_URL))
$(verbose) chmod +x $(RELX)
relx-rel: $(RELX)
$(verbose) $(RELX) -c $(RELX_CONFIG) $(RELX_OPTS)
distclean-relx-rel:
$(gen_verbose) rm -rf $(RELX_OUTPUT_DIR)
distclean-relx:
$(gen_verbose) rm -rf $(RELX)
# Run target.
ifeq ($(wildcard $(RELX_CONFIG)),)
run:
else
define get_relx_release.erl
{ok, Config} = file:consult("$(RELX_CONFIG)"),
{release, {Name, _}, _} = lists:keyfind(release, 1, Config),
io:format("~s", [Name]),
halt(0).
endef
RELX_RELEASE = `$(call erlang,$(get_relx_release.erl))`
run: all
$(verbose) $(RELX_OUTPUT_DIR)/$(RELX_RELEASE)/bin/$(RELX_RELEASE) console
help::
$(verbose) printf "%s\n" "" \
"Relx targets:" \
" run Compile the project, build the release and run it"
endif
# Copyright (c) 2014, M Robert Martin <rob@version2beta.com>
# This file is contributed to erlang.mk and subject to the terms of the ISC License.
.PHONY: shell
# Configuration.
SHELL_PATH ?= -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin
SHELL_OPTS ?=
ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))
# Core targets
help::
$(verbose) printf "%s\n" "" \
"Shell targets:" \
" shell Run an erlang shell with SHELL_OPTS or reasonable default"
# Plugin-specific targets.
$(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep))))
build-shell-deps: $(ALL_SHELL_DEPS_DIRS)
$(verbose) for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done
shell: build-shell-deps
$(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS)
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
ifneq ($(wildcard $(DEPS_DIR)/triq),)
.PHONY: triq
# Targets.
tests:: triq
define triq_check.erl
code:add_pathsa(["$(CURDIR)/ebin", "$(DEPS_DIR)/*/ebin"]),
try
case $(1) of
all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]);
module -> triq:check($(2));
function -> triq:check($(2))
end
of
true -> halt(0);
_ -> halt(1)
catch error:undef ->
io:format("Undefined property or module~n"),
halt(0)
end.
endef
ifdef t
ifeq (,$(findstring :,$(t)))
triq: test-build
$(verbose) $(call erlang,$(call triq_check.erl,module,$(t)))
else
triq: test-build
$(verbose) echo Testing $(t)/0
$(verbose) $(call erlang,$(call triq_check.erl,function,$(t)()))
endif
else
triq: test-build
$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam))))))
$(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES)))
endif
endif
# Copyright (c) 2015, Erlang Solutions Ltd.
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: xref distclean-xref
# Configuration.
ifeq ($(XREF_CONFIG),)
XREF_ARGS :=
else
XREF_ARGS := -c $(XREF_CONFIG)
endif
XREFR ?= $(CURDIR)/xrefr
export XREFR
XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/0.2.2/xrefr
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Xref targets:" \
" xref Run Xrefr using $XREF_CONFIG as config file if defined"
distclean:: distclean-xref
# Plugin-specific targets.
$(XREFR):
$(gen_verbose) $(call core_http_get,$(XREFR),$(XREFR_URL))
$(verbose) chmod +x $(XREFR)
xref: deps app $(XREFR)
$(gen_verbose) $(XREFR) $(XREFR_ARGS)
distclean-xref:
$(gen_verbose) rm -rf $(XREFR)
# Copyright 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
# This file is part of erlang.mk and subject to the terms of the ISC License.
COVER_REPORT_DIR = cover
# Hook in coverage to ct
ifdef COVER
ifdef CT_RUN
# All modules in 'ebin'
COVER_MODS = $(notdir $(basename $(call core_ls,ebin/*.beam)))
test-build:: $(TEST_DIR)/ct.cover.spec
$(TEST_DIR)/ct.cover.spec:
$(verbose) echo Cover mods: $(COVER_MODS)
$(gen_verbose) printf "%s\n" \
'{incl_mods,[$(subst $(space),$(comma),$(COVER_MODS))]}.' \
'{export,"$(CURDIR)/ct.coverdata"}.' > $@
CT_RUN += -cover $(TEST_DIR)/ct.cover.spec
endif
endif
# Core targets
ifdef COVER
ifneq ($(COVER_REPORT_DIR),)
tests::
$(verbose) $(MAKE) --no-print-directory cover-report
endif
endif
clean:: coverdata-clean
ifneq ($(COVER_REPORT_DIR),)
distclean:: cover-report-clean
endif
help::
$(verbose) printf "%s\n" "" \
"Cover targets:" \
" cover-report Generate a HTML coverage report from previously collected" \
" cover data." \
" all.coverdata Merge {eunit,ct}.coverdata into one coverdata file." \
"" \
"If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \
"target tests additionally generates a HTML coverage report from the combined" \
"coverdata files from each of these testing tools. HTML reports can be disabled" \
"by setting COVER_REPORT_DIR to empty."
# Plugin specific targets
COVERDATA = $(filter-out all.coverdata,$(wildcard *.coverdata))
.PHONY: coverdata-clean
coverdata-clean:
$(gen_verbose) rm -f *.coverdata ct.cover.spec
# Merge all coverdata files into one.
all.coverdata: $(COVERDATA)
$(gen_verbose) $(ERL) -eval ' \
$(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) \
cover:export("$@"), halt(0).'
# These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to
# empty if you want the coverdata files but not the HTML report.
ifneq ($(COVER_REPORT_DIR),)
.PHONY: cover-report-clean cover-report
cover-report-clean:
$(gen_verbose) rm -rf $(COVER_REPORT_DIR)
ifeq ($(COVERDATA),)
cover-report:
else
# Modules which include eunit.hrl always contain one line without coverage
# because eunit defines test/0 which is never called. We compensate for this.
EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \
grep -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \
| sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq))
define cover_report.erl
$(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
Ms = cover:imported_modules(),
[cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M)
++ ".COVER.html", [html]) || M <- Ms],
Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],
EunitHrlMods = [$(EUNIT_HRL_MODS)],
Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of
true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],
TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),
TotalN = lists:sum([N || {_, {_, N}} <- Report1]),
TotalPerc = round(100 * TotalY / (TotalY + TotalN)),
{ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]),
io:format(F, "<!DOCTYPE html><html>~n"
"<head><meta charset=\"UTF-8\">~n"
"<title>Coverage report</title></head>~n"
"<body>~n", []),
io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
[io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
"<td>~p%</td></tr>~n",
[M, M, round(100 * Y / (Y + N))]) || {M, {Y, N}} <- Report1],
How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))",
Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")",
io:format(F, "</table>~n"
"<p>Generated using ~s and erlang.mk on ~s.</p>~n"
"</body></html>", [How, Date]),
halt().
endef
cover-report:
$(gen_verbose) mkdir -p $(COVER_REPORT_DIR)
$(gen_verbose) $(call erlang,$(cover_report.erl))
endif
endif # ifneq ($(COVER_REPORT_DIR),)
|
> > > > | 1 2 3 4 |
{deps, [
{cowlib, ".*", {git, "https://github.com/ninenines/cowlib.git", "1.0.0"}},
{ranch, ".*", {git, "https://github.com/ninenines/ranch.git", "1.0.0"}}
]}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
{application, cowboy, [
{description, "Small, fast, modular HTTP server."},
{vsn, "1.0.4"},
{id, "git"},
{modules, []},
{registered, [cowboy_clock, cowboy_sup]},
{applications, [
kernel,
stdlib,
ranch,
cowlib,
crypto
]},
{mod, {cowboy_app, []}},
{env, []}
]}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy).
-export([start_http/4]).
-export([start_https/4]).
-export([start_spdy/4]).
-export([stop_listener/1]).
-export([set_env/3]).
-type http_headers() :: [{binary(), iodata()}].
-export_type([http_headers/0]).
-type http_status() :: non_neg_integer() | binary().
-export_type([http_status/0]).
-type http_version() :: 'HTTP/1.1' | 'HTTP/1.0'.
-export_type([http_version/0]).
-type onrequest_fun() :: fun((Req) -> Req).
-export_type([onrequest_fun/0]).
-type onresponse_fun() ::
fun((http_status(), http_headers(), iodata(), Req) -> Req).
-export_type([onresponse_fun/0]).
-spec start_http(ranch:ref(), non_neg_integer(), ranch_tcp:opts(),
cowboy_protocol:opts()) -> {ok, pid()} | {error, any()}.
start_http(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
ranch:start_listener(Ref, NbAcceptors,
ranch_tcp, TransOpts, cowboy_protocol, ProtoOpts).
-spec start_https(ranch:ref(), non_neg_integer(), ranch_ssl:opts(),
cowboy_protocol:opts()) -> {ok, pid()} | {error, any()}.
start_https(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
ranch:start_listener(Ref, NbAcceptors,
ranch_ssl, TransOpts, cowboy_protocol, ProtoOpts).
-spec start_spdy(ranch:ref(), non_neg_integer(), ranch_ssl:opts(),
cowboy_spdy:opts()) -> {ok, pid()} | {error, any()}.
start_spdy(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
TransOpts2 = [
{connection_type, supervisor},
{next_protocols_advertised,
[<<"spdy/3">>, <<"http/1.1">>, <<"http/1.0">>]}
|TransOpts],
ranch:start_listener(Ref, NbAcceptors,
ranch_ssl, TransOpts2, cowboy_spdy, ProtoOpts).
-spec stop_listener(ranch:ref()) -> ok | {error, not_found}.
stop_listener(Ref) ->
ranch:stop_listener(Ref).
-spec set_env(ranch:ref(), atom(), any()) -> ok.
set_env(Ref, Name, Value) ->
Opts = ranch:get_protocol_options(Ref),
{_, Env} = lists:keyfind(env, 1, Opts),
Opts2 = lists:keyreplace(env, 1, Opts,
{env, lists:keystore(Name, 1, Env, {Name, Value})}),
ok = ranch:set_protocol_options(Ref, Opts2).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_app).
-behaviour(application).
-export([start/2]).
-export([stop/1]).
-spec start(_, _) -> {ok, pid()}.
start(_, _) ->
cowboy_sup:start_link().
-spec stop(_) -> ok.
stop(_) ->
ok.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_bstr).
%% Binary strings.
-export([capitalize_token/1]).
-export([to_lower/1]).
-export([to_upper/1]).
%% Characters.
-export([char_to_lower/1]).
-export([char_to_upper/1]).
%% The first letter and all letters after a dash are capitalized.
%% This is the form seen for header names in the HTTP/1.1 RFC and
%% others. Note that using this form isn't required, as header names
%% are case insensitive, and it is only provided for use with eventual
%% badly implemented clients.
-spec capitalize_token(B) -> B when B::binary().
capitalize_token(B) ->
capitalize_token(B, true, <<>>).
capitalize_token(<<>>, _, Acc) ->
Acc;
capitalize_token(<< $-, Rest/bits >>, _, Acc) ->
capitalize_token(Rest, true, << Acc/binary, $- >>);
capitalize_token(<< C, Rest/bits >>, true, Acc) ->
capitalize_token(Rest, false, << Acc/binary, (char_to_upper(C)) >>);
capitalize_token(<< C, Rest/bits >>, false, Acc) ->
capitalize_token(Rest, false, << Acc/binary, (char_to_lower(C)) >>).
-spec to_lower(B) -> B when B::binary().
to_lower(B) ->
<< << (char_to_lower(C)) >> || << C >> <= B >>.
-spec to_upper(B) -> B when B::binary().
to_upper(B) ->
<< << (char_to_upper(C)) >> || << C >> <= B >>.
-spec char_to_lower(char()) -> char().
char_to_lower($A) -> $a;
char_to_lower($B) -> $b;
char_to_lower($C) -> $c;
char_to_lower($D) -> $d;
char_to_lower($E) -> $e;
char_to_lower($F) -> $f;
char_to_lower($G) -> $g;
char_to_lower($H) -> $h;
char_to_lower($I) -> $i;
char_to_lower($J) -> $j;
char_to_lower($K) -> $k;
char_to_lower($L) -> $l;
char_to_lower($M) -> $m;
char_to_lower($N) -> $n;
char_to_lower($O) -> $o;
char_to_lower($P) -> $p;
char_to_lower($Q) -> $q;
char_to_lower($R) -> $r;
char_to_lower($S) -> $s;
char_to_lower($T) -> $t;
char_to_lower($U) -> $u;
char_to_lower($V) -> $v;
char_to_lower($W) -> $w;
char_to_lower($X) -> $x;
char_to_lower($Y) -> $y;
char_to_lower($Z) -> $z;
char_to_lower(Ch) -> Ch.
-spec char_to_upper(char()) -> char().
char_to_upper($a) -> $A;
char_to_upper($b) -> $B;
char_to_upper($c) -> $C;
char_to_upper($d) -> $D;
char_to_upper($e) -> $E;
char_to_upper($f) -> $F;
char_to_upper($g) -> $G;
char_to_upper($h) -> $H;
char_to_upper($i) -> $I;
char_to_upper($j) -> $J;
char_to_upper($k) -> $K;
char_to_upper($l) -> $L;
char_to_upper($m) -> $M;
char_to_upper($n) -> $N;
char_to_upper($o) -> $O;
char_to_upper($p) -> $P;
char_to_upper($q) -> $Q;
char_to_upper($r) -> $R;
char_to_upper($s) -> $S;
char_to_upper($t) -> $T;
char_to_upper($u) -> $U;
char_to_upper($v) -> $V;
char_to_upper($w) -> $W;
char_to_upper($x) -> $X;
char_to_upper($y) -> $Y;
char_to_upper($z) -> $Z;
char_to_upper(Ch) -> Ch.
%% Tests.
-ifdef(TEST).
capitalize_token_test_() ->
Tests = [
{<<"heLLo-woRld">>, <<"Hello-World">>},
{<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
{<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
{<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
{<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
{<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--Version">>},
{<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
],
[{H, fun() -> R = capitalize_token(H) end} || {H, R} <- Tests].
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% While a gen_server process runs in the background to update
%% the cache of formatted dates every second, all API calls are
%% local and directly read from the ETS cache table, providing
%% fast time and date computations.
-module(cowboy_clock).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
-export([stop/0]).
-export([rfc1123/0]).
-export([rfc1123/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {
universaltime = undefined :: undefined | calendar:datetime(),
rfc1123 = <<>> :: binary(),
tref = undefined :: undefined | reference()
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec stop() -> stopped.
stop() ->
gen_server:call(?MODULE, stop).
-spec rfc1123() -> binary().
rfc1123() ->
ets:lookup_element(?MODULE, rfc1123, 2).
-spec rfc1123(calendar:datetime()) -> binary().
rfc1123(DateTime) ->
update_rfc1123(<<>>, undefined, DateTime).
%% gen_server.
-spec init([]) -> {ok, #state{}}.
init([]) ->
?MODULE = ets:new(?MODULE, [set, protected,
named_table, {read_concurrency, true}]),
T = erlang:universaltime(),
B = update_rfc1123(<<>>, undefined, T),
TRef = erlang:send_after(1000, self(), update),
ets:insert(?MODULE, {rfc1123, B}),
{ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
-type from() :: {pid(), term()}.
-spec handle_call
(stop, from(), State) -> {stop, normal, stopped, State}
when State::#state{}.
handle_call(stop, _From, State) ->
{stop, normal, stopped, State};
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
-spec handle_cast(_, State) -> {noreply, State} when State::#state{}.
handle_cast(_Msg, State) ->
{noreply, State}.
-spec handle_info(any(), State) -> {noreply, State} when State::#state{}.
handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef0}) ->
%% Cancel the timer in case an external process sent an update message.
_ = erlang:cancel_timer(TRef0),
T = erlang:universaltime(),
B2 = update_rfc1123(B1, Prev, T),
ets:insert(?MODULE, {rfc1123, B2}),
TRef = erlang:send_after(1000, self(), update),
{noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
handle_info(_Info, State) ->
{noreply, State}.
-spec terminate(_, _) -> ok.
terminate(_Reason, _State) ->
ok.
-spec code_change(_, State, _) -> {ok, State} when State::#state{}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Internal.
-spec update_rfc1123(binary(), undefined | calendar:datetime(),
calendar:datetime()) -> binary().
update_rfc1123(Bin, Now, Now) ->
Bin;
update_rfc1123(<< Keep:23/binary, _/bits >>,
{Date, {H, M, _}}, {Date, {H, M, S}}) ->
<< Keep/binary, (pad_int(S))/binary, " GMT" >>;
update_rfc1123(<< Keep:20/binary, _/bits >>,
{Date, {H, _, _}}, {Date, {H, M, S}}) ->
<< Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>;
update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) ->
<< Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,
$:, (pad_int(S))/binary, " GMT" >>;
update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>,
{{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
Wday = calendar:day_of_the_week(Date),
<< (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary,
(pad_int(H))/binary, $:, (pad_int(M))/binary,
$:, (pad_int(S))/binary, " GMT" >>;
update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>,
{{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
Wday = calendar:day_of_the_week(Date),
<< (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
(month(Mo))/binary, Keep/binary,
(pad_int(H))/binary, $:, (pad_int(M))/binary,
$:, (pad_int(S))/binary, " GMT" >>;
update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) ->
Wday = calendar:day_of_the_week(Date),
<< (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
(month(Mo))/binary, " ", (integer_to_binary(Y))/binary,
" ", (pad_int(H))/binary, $:, (pad_int(M))/binary,
$:, (pad_int(S))/binary, " GMT" >>.
%% Following suggestion by MononcQc on #erlounge.
-spec pad_int(0..59) -> binary().
pad_int(X) when X < 10 ->
<< $0, ($0 + X) >>;
pad_int(X) ->
integer_to_binary(X).
-spec weekday(1..7) -> <<_:24>>.
weekday(1) -> <<"Mon">>;
weekday(2) -> <<"Tue">>;
weekday(3) -> <<"Wed">>;
weekday(4) -> <<"Thu">>;
weekday(5) -> <<"Fri">>;
weekday(6) -> <<"Sat">>;
weekday(7) -> <<"Sun">>.
-spec month(1..12) -> <<_:24>>.
month( 1) -> <<"Jan">>;
month( 2) -> <<"Feb">>;
month( 3) -> <<"Mar">>;
month( 4) -> <<"Apr">>;
month( 5) -> <<"May">>;
month( 6) -> <<"Jun">>;
month( 7) -> <<"Jul">>;
month( 8) -> <<"Aug">>;
month( 9) -> <<"Sep">>;
month(10) -> <<"Oct">>;
month(11) -> <<"Nov">>;
month(12) -> <<"Dec">>.
%% Tests.
-ifdef(TEST).
update_rfc1123_test_() ->
Tests = [
{<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined,
{{2011, 5, 14}, {14, 25, 33}}, <<>>},
{<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
{{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
{<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
{{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
{<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}},
{{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>},
{<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}},
{{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>},
{<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}},
{{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>},
{<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
{{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>},
{<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
{{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>}
],
[{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests].
pad_int_test_() ->
Tests = [
{ 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>},
{ 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>},
{ 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>},
{12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>},
{16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>},
{20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>},
{24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>},
{28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>},
{32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>},
{36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>},
{40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>},
{44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>},
{48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>},
{52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>},
{56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>}
],
[{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% Handler middleware.
%%
%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>
%% environment values. The result of this execution is added to the
%% environment under the <em>result</em> value.
%%
%% When using loop handlers, we are receiving data from the socket because we
%% want to know when the socket gets closed. This is generally not an issue
%% because these kinds of requests are generally not pipelined, and don't have
%% a body. If they do have a body, this body is often read in the
%% <em>init/3</em> callback and this is no problem. Otherwise, this data
%% accumulates in a buffer until we reach a certain threshold of 5000 bytes
%% by default. This can be configured through the <em>loop_max_buffer</em>
%% environment value. The request will be terminated with an
%% <em>{error, overflow}</em> reason if this threshold is reached.
-module(cowboy_handler).
-behaviour(cowboy_middleware).
-export([execute/2]).
-export([handler_loop/4]).
-record(state, {
env :: cowboy_middleware:env(),
hibernate = false :: boolean(),
loop_buffer_size = 0 :: non_neg_integer(),
loop_max_buffer = 5000 :: non_neg_integer() | infinity,
loop_timeout = infinity :: timeout(),
loop_timeout_ref = undefined :: undefined | reference(),
resp_sent = false :: boolean()
}).
-spec execute(Req, Env)
-> {ok, Req, Env} | {suspend, ?MODULE, handler_loop, [any()]}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
execute(Req, Env) ->
{_, Handler} = lists:keyfind(handler, 1, Env),
{_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env),
MaxBuffer = case lists:keyfind(loop_max_buffer, 1, Env) of
false -> 5000;
{_, MaxBuffer0} -> MaxBuffer0
end,
handler_init(Req, #state{env=Env, loop_max_buffer=MaxBuffer},
Handler, HandlerOpts).
-spec handler_init(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_init(Req, State, Handler, HandlerOpts) ->
Transport = cowboy_req:get(transport, Req),
try Handler:init({Transport:name(), http}, Req, HandlerOpts) of
{ok, Req2, HandlerState} ->
handler_handle(Req2, State, Handler, HandlerState);
{loop, Req2, HandlerState} ->
handler_after_callback(Req2, State, Handler, HandlerState);
{loop, Req2, HandlerState, hibernate} ->
handler_after_callback(Req2, State#state{hibernate=true},
Handler, HandlerState);
{loop, Req2, HandlerState, Timeout} ->
State2 = handler_loop_timeout(State#state{loop_timeout=Timeout}),
handler_after_callback(Req2, State2, Handler, HandlerState);
{loop, Req2, HandlerState, Timeout, hibernate} ->
State2 = handler_loop_timeout(State#state{
hibernate=true, loop_timeout=Timeout}),
handler_after_callback(Req2, State2, Handler, HandlerState);
{shutdown, Req2, HandlerState} ->
terminate_request(Req2, State, Handler, HandlerState,
{normal, shutdown});
{upgrade, protocol, Module} ->
upgrade_protocol(Req, State, Handler, HandlerOpts, Module);
{upgrade, protocol, Module, Req2, HandlerOpts2} ->
upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module)
catch Class:Reason ->
Stacktrace = erlang:get_stacktrace(),
cowboy_req:maybe_reply(Stacktrace, Req),
erlang:Class([
{reason, Reason},
{mfa, {Handler, init, 3}},
{stacktrace, Stacktrace},
{req, cowboy_req:to_list(Req)},
{opts, HandlerOpts}
])
end.
-spec upgrade_protocol(Req, #state{}, module(), any(), module())
-> {ok, Req, Env}
| {suspend, module(), atom(), any()}
| {halt, Req}
| {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade_protocol(Req, #state{env=Env},
Handler, HandlerOpts, Module) ->
Module:upgrade(Req, Env, Handler, HandlerOpts).
-spec handler_handle(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
handler_handle(Req, State, Handler, HandlerState) ->
try Handler:handle(Req, HandlerState) of
{ok, Req2, HandlerState2} ->
terminate_request(Req2, State, Handler, HandlerState2,
{normal, shutdown})
catch Class:Reason ->
Stacktrace = erlang:get_stacktrace(),
cowboy_req:maybe_reply(Stacktrace, Req),
handler_terminate(Req, Handler, HandlerState, Reason),
erlang:Class([
{reason, Reason},
{mfa, {Handler, handle, 2}},
{stacktrace, Stacktrace},
{req, cowboy_req:to_list(Req)},
{state, HandlerState}
])
end.
%% Update the state if the response was sent in the callback.
-spec handler_after_callback(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_after_callback(Req, State=#state{resp_sent=false}, Handler,
HandlerState) ->
receive
{cowboy_req, resp_sent} ->
handler_before_loop(Req, State#state{resp_sent=true}, Handler,
HandlerState)
after 0 ->
handler_before_loop(Req, State, Handler, HandlerState)
end;
handler_after_callback(Req, State, Handler, HandlerState) ->
handler_before_loop(Req, State, Handler, HandlerState).
-spec handler_before_loop(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
Transport:setopts(Socket, [{active, once}]),
{suspend, ?MODULE, handler_loop,
[Req, State#state{hibernate=false}, Handler, HandlerState]};
handler_before_loop(Req, State, Handler, HandlerState) ->
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
Transport:setopts(Socket, [{active, once}]),
handler_loop(Req, State, Handler, HandlerState).
%% Almost the same code can be found in cowboy_websocket.
-spec handler_loop_timeout(#state{}) -> #state{}.
handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
State#state{loop_timeout_ref=undefined};
handler_loop_timeout(State=#state{loop_timeout=Timeout,
loop_timeout_ref=PrevRef}) ->
_ = case PrevRef of
undefined -> ignore;
PrevRef -> erlang:cancel_timer(PrevRef)
end,
TRef = erlang:start_timer(Timeout, self(), ?MODULE),
State#state{loop_timeout_ref=TRef}.
-spec handler_loop(Req, #state{}, module(), any())
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_loop(Req, State=#state{loop_buffer_size=NbBytes,
loop_max_buffer=Threshold, loop_timeout_ref=TRef,
resp_sent=RespSent}, Handler, HandlerState) ->
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
{OK, Closed, Error} = Transport:messages(),
receive
{OK, Socket, Data} ->
NbBytes2 = NbBytes + byte_size(Data),
if NbBytes2 > Threshold ->
_ = handler_terminate(Req, Handler, HandlerState,
{error, overflow}),
_ = if RespSent -> ok; true ->
cowboy_req:reply(500, Req)
end,
exit(normal);
true ->
Req2 = cowboy_req:append_buffer(Data, Req),
State2 = handler_loop_timeout(State#state{
loop_buffer_size=NbBytes2}),
handler_before_loop(Req2, State2, Handler, HandlerState)
end;
{Closed, Socket} ->
terminate_request(Req, State, Handler, HandlerState,
{error, closed});
{Error, Socket, Reason} ->
terminate_request(Req, State, Handler, HandlerState,
{error, Reason});
{timeout, TRef, ?MODULE} ->
handler_after_loop(Req, State, Handler, HandlerState,
{normal, timeout});
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
handler_loop(Req, State, Handler, HandlerState);
Message ->
%% We set the socket back to {active, false} mode in case
%% the handler is going to call recv. We also flush any
%% data received after that and put it into the buffer.
%% We do not check the size here, if data keeps coming
%% we'll error out on the next packet received.
Transport:setopts(Socket, [{active, false}]),
Req2 = receive {OK, Socket, Data} ->
cowboy_req:append_buffer(Data, Req)
after 0 ->
Req
end,
handler_call(Req2, State, Handler, HandlerState, Message)
end.
-spec handler_call(Req, #state{}, module(), any(), any())
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_call(Req, State=#state{resp_sent=RespSent},
Handler, HandlerState, Message) ->
try Handler:info(Message, Req, HandlerState) of
{ok, Req2, HandlerState2} ->
handler_after_loop(Req2, State, Handler, HandlerState2,
{normal, shutdown});
{loop, Req2, HandlerState2} ->
handler_after_callback(Req2, State, Handler, HandlerState2);
{loop, Req2, HandlerState2, hibernate} ->
handler_after_callback(Req2, State#state{hibernate=true},
Handler, HandlerState2)
catch Class:Reason ->
Stacktrace = erlang:get_stacktrace(),
if RespSent -> ok; true ->
cowboy_req:maybe_reply(Stacktrace, Req)
end,
handler_terminate(Req, Handler, HandlerState, Reason),
erlang:Class([
{reason, Reason},
{mfa, {Handler, info, 3}},
{stacktrace, Stacktrace},
{req, cowboy_req:to_list(Req)},
{state, HandlerState}
])
end.
%% It is sometimes important to make a socket passive as it was initially
%% and as it is expected to be by cowboy_protocol, right after we're done
%% with loop handling. The browser may freely pipeline a bunch of requests
%% if previous one was, say, a JSONP long-polling request.
-spec handler_after_loop(Req, #state{}, module(), any(),
{normal, timeout | shutdown} | {error, atom()}) ->
{ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
handler_after_loop(Req, State, Handler, HandlerState, Reason) ->
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
Transport:setopts(Socket, [{active, false}]),
{OK, _Closed, _Error} = Transport:messages(),
Req2 = receive
{OK, Socket, Data} ->
cowboy_req:append_buffer(Data, Req)
after 0 ->
Req
end,
terminate_request(Req2, State, Handler, HandlerState, Reason).
-spec terminate_request(Req, #state{}, module(), any(),
{normal, timeout | shutdown} | {error, atom()}) ->
{ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
terminate_request(Req, #state{env=Env, loop_timeout_ref=TRef},
Handler, HandlerState, Reason) ->
HandlerRes = handler_terminate(Req, Handler, HandlerState, Reason),
_ = case TRef of
undefined -> ignore;
TRef -> erlang:cancel_timer(TRef)
end,
flush_timeouts(),
{ok, Req, [{result, HandlerRes}|Env]}.
-spec handler_terminate(cowboy_req:req(), module(), any(),
{normal, timeout | shutdown} | {error, atom()}) -> ok.
handler_terminate(Req, Handler, HandlerState, Reason) ->
try
Handler:terminate(Reason, cowboy_req:lock(Req), HandlerState)
catch Class:Reason2 ->
erlang:Class([
{reason, Reason2},
{mfa, {Handler, terminate, 3}},
{stacktrace, erlang:get_stacktrace()},
{req, cowboy_req:to_list(Req)},
{state, HandlerState},
{terminate_reason, Reason}
])
end.
-spec flush_timeouts() -> ok.
flush_timeouts() ->
receive
{timeout, TRef, ?MODULE} when is_reference(TRef) ->
flush_timeouts()
after 0 ->
ok
end.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% Deprecated HTTP parsing API.
-module(cowboy_http).
%% Parsing.
-export([list/2]).
-export([nonempty_list/2]).
-export([content_type/1]).
-export([media_range/2]).
-export([conneg/2]).
-export([language_range/2]).
-export([entity_tag_match/1]).
-export([expectation/2]).
-export([params/2]).
-export([http_date/1]).
-export([rfc1123_date/1]).
-export([rfc850_date/1]).
-export([asctime_date/1]).
-export([whitespace/2]).
-export([digits/1]).
-export([token/2]).
-export([token_ci/2]).
-export([quoted_string/2]).
-export([authorization/2]).
-export([range/1]).
-export([parameterized_tokens/1]).
%% Decoding.
-export([ce_identity/1]).
%% Parsing.
-spec nonempty_list(binary(), fun()) -> [any(), ...] | {error, badarg}.
nonempty_list(Data, Fun) ->
case list(Data, Fun, []) of
{error, badarg} -> {error, badarg};
[] -> {error, badarg};
L -> lists:reverse(L)
end.
-spec list(binary(), fun()) -> list() | {error, badarg}.
list(Data, Fun) ->
case list(Data, Fun, []) of
{error, badarg} -> {error, badarg};
L -> lists:reverse(L)
end.
-spec list(binary(), fun(), [binary()]) -> [any()] | {error, badarg}.
%% From the RFC:
%% <blockquote>Wherever this construct is used, null elements are allowed,
%% but do not contribute to the count of elements present.
%% That is, "(element), , (element) " is permitted, but counts
%% as only two elements. Therefore, where at least one element is required,
%% at least one non-null element MUST be present.</blockquote>
list(Data, Fun, Acc) ->
whitespace(Data,
fun (<<>>) -> Acc;
(<< $,, Rest/binary >>) -> list(Rest, Fun, Acc);
(Rest) -> Fun(Rest,
fun (D, I) -> whitespace(D,
fun (<<>>) -> [I|Acc];
(<< $,, R/binary >>) -> list(R, Fun, [I|Acc]);
(_Any) -> {error, badarg}
end)
end)
end).
%% We lowercase the charset header as we know it's case insensitive.
-spec content_type(binary()) -> any().
content_type(Data) ->
media_type(Data,
fun (Rest, Type, SubType) ->
params(Rest,
fun (<<>>, Params) ->
case lists:keyfind(<<"charset">>, 1, Params) of
false ->
{Type, SubType, Params};
{_, Charset} ->
Charset2 = cowboy_bstr:to_lower(Charset),
Params2 = lists:keyreplace(<<"charset">>,
1, Params, {<<"charset">>, Charset2}),
{Type, SubType, Params2}
end;
(_Rest2, _) ->
{error, badarg}
end)
end).
-spec media_range(binary(), fun()) -> any().
media_range(Data, Fun) ->
media_type(Data,
fun (Rest, Type, SubType) ->
media_range_params(Rest, Fun, Type, SubType, [])
end).
-spec media_range_params(binary(), fun(), binary(), binary(),
[{binary(), binary()}]) -> any().
media_range_params(Data, Fun, Type, SubType, Acc) ->
whitespace(Data,
fun (<< $;, Rest/binary >>) ->
whitespace(Rest,
fun (Rest2) ->
media_range_param_attr(Rest2, Fun, Type, SubType, Acc)
end);
(Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Acc)}, 1000, []})
end).
-spec media_range_param_attr(binary(), fun(), binary(), binary(),
[{binary(), binary()}]) -> any().
media_range_param_attr(Data, Fun, Type, SubType, Acc) ->
token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $=, Rest/binary >>, Attr) ->
media_range_param_value(Rest, Fun, Type, SubType, Acc, Attr)
end).
-spec media_range_param_value(binary(), fun(), binary(), binary(),
[{binary(), binary()}], binary()) -> any().
media_range_param_value(Data, Fun, Type, SubType, Acc, <<"q">>) ->
qvalue(Data,
fun (Rest, Quality) ->
accept_ext(Rest, Fun, Type, SubType, Acc, Quality, [])
end);
media_range_param_value(Data, Fun, Type, SubType, Acc, Attr) ->
word(Data,
fun (Rest, Value) ->
media_range_params(Rest, Fun,
Type, SubType, [{Attr, Value}|Acc])
end).
-spec media_type(binary(), fun()) -> any().
media_type(Data, Fun) ->
token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $/, Rest/binary >>, Type) ->
token_ci(Rest,
fun (_Rest2, <<>>) -> {error, badarg};
(Rest2, SubType) -> Fun(Rest2, Type, SubType)
end);
%% This is a non-strict parsing clause required by some user agents
%% that use * instead of */* in the list of media types.
(Rest, <<"*">> = Type) ->
token_ci(<<"*", Rest/binary>>,
fun (_Rest2, <<>>) -> {error, badarg};
(Rest2, SubType) -> Fun(Rest2, Type, SubType)
end);
(_Rest, _Type) -> {error, badarg}
end).
-spec accept_ext(binary(), fun(), binary(), binary(),
[{binary(), binary()}], 0..1000,
[{binary(), binary()} | binary()]) -> any().
accept_ext(Data, Fun, Type, SubType, Params, Quality, Acc) ->
whitespace(Data,
fun (<< $;, Rest/binary >>) ->
whitespace(Rest,
fun (Rest2) ->
accept_ext_attr(Rest2, Fun,
Type, SubType, Params, Quality, Acc)
end);
(Rest) ->
Fun(Rest, {{Type, SubType, lists:reverse(Params)},
Quality, lists:reverse(Acc)})
end).
-spec accept_ext_attr(binary(), fun(), binary(), binary(),
[{binary(), binary()}], 0..1000,
[{binary(), binary()} | binary()]) -> any().
accept_ext_attr(Data, Fun, Type, SubType, Params, Quality, Acc) ->
token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $=, Rest/binary >>, Attr) ->
accept_ext_value(Rest, Fun, Type, SubType, Params,
Quality, Acc, Attr);
(Rest, Attr) ->
accept_ext(Rest, Fun, Type, SubType, Params,
Quality, [Attr|Acc])
end).
-spec accept_ext_value(binary(), fun(), binary(), binary(),
[{binary(), binary()}], 0..1000,
[{binary(), binary()} | binary()], binary()) -> any().
accept_ext_value(Data, Fun, Type, SubType, Params, Quality, Acc, Attr) ->
word(Data,
fun (Rest, Value) ->
accept_ext(Rest, Fun,
Type, SubType, Params, Quality, [{Attr, Value}|Acc])
end).
-spec conneg(binary(), fun()) -> any().
conneg(Data, Fun) ->
token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(Rest, Conneg) ->
maybe_qparam(Rest,
fun (Rest2, Quality) ->
Fun(Rest2, {Conneg, Quality})
end)
end).
-spec language_range(binary(), fun()) -> any().
language_range(<< $*, Rest/binary >>, Fun) ->
language_range_ret(Rest, Fun, '*');
language_range(Data, Fun) ->
language_tag(Data,
fun (Rest, LanguageTag) ->
language_range_ret(Rest, Fun, LanguageTag)
end).
-spec language_range_ret(binary(), fun(), '*' | {binary(), [binary()]}) -> any().
language_range_ret(Data, Fun, LanguageTag) ->
maybe_qparam(Data,
fun (Rest, Quality) ->
Fun(Rest, {LanguageTag, Quality})
end).
-spec language_tag(binary(), fun()) -> any().
language_tag(Data, Fun) ->
alpha(Data,
fun (_Rest, Tag) when byte_size(Tag) =:= 0; byte_size(Tag) > 8 ->
{error, badarg};
(<< $-, Rest/binary >>, Tag) ->
language_subtag(Rest, Fun, Tag, []);
(Rest, Tag) ->
Fun(Rest, Tag)
end).
-spec language_subtag(binary(), fun(), binary(), [binary()]) -> any().
language_subtag(Data, Fun, Tag, Acc) ->
alphanumeric(Data,
fun (_Rest, SubTag) when byte_size(SubTag) =:= 0;
byte_size(SubTag) > 8 -> {error, badarg};
(<< $-, Rest/binary >>, SubTag) ->
language_subtag(Rest, Fun, Tag, [SubTag|Acc]);
(Rest, SubTag) ->
%% Rebuild the full tag now that we know it's correct
Sub = << << $-, S/binary >> || S <- lists:reverse([SubTag|Acc]) >>,
Fun(Rest, << Tag/binary, Sub/binary >>)
end).
-spec maybe_qparam(binary(), fun()) -> any().
maybe_qparam(Data, Fun) ->
whitespace(Data,
fun (<< $;, Rest/binary >>) ->
whitespace(Rest,
fun (Rest2) ->
%% This is a non-strict parsing clause required by some user agents
%% that use the wrong delimiter putting a charset where a qparam is
%% expected.
try qparam(Rest2, Fun) of
Result -> Result
catch
error:function_clause ->
Fun(<<",", Rest2/binary>>, 1000)
end
end);
(Rest) ->
Fun(Rest, 1000)
end).
-spec qparam(binary(), fun()) -> any().
qparam(<< Q, $=, Data/binary >>, Fun) when Q =:= $q; Q =:= $Q ->
qvalue(Data, Fun).
-spec entity_tag_match(binary()) -> any().
entity_tag_match(<< $*, Rest/binary >>) ->
whitespace(Rest,
fun (<<>>) -> '*';
(_Any) -> {error, badarg}
end);
entity_tag_match(Data) ->
nonempty_list(Data, fun entity_tag/2).
-spec entity_tag(binary(), fun()) -> any().
entity_tag(<< "W/", Rest/binary >>, Fun) ->
opaque_tag(Rest, Fun, weak);
entity_tag(Data, Fun) ->
opaque_tag(Data, Fun, strong).
-spec opaque_tag(binary(), fun(), weak | strong) -> any().
opaque_tag(Data, Fun, Strength) ->
quoted_string(Data,
fun (_Rest, <<>>) -> {error, badarg};
(Rest, OpaqueTag) -> Fun(Rest, {Strength, OpaqueTag})
end).
-spec expectation(binary(), fun()) -> any().
expectation(Data, Fun) ->
token_ci(Data,
fun (_Rest, <<>>) -> {error, badarg};
(<< $=, Rest/binary >>, Expectation) ->
word(Rest,
fun (Rest2, ExtValue) ->
params(Rest2, fun (Rest3, ExtParams) ->
Fun(Rest3, {Expectation, ExtValue, ExtParams})
end)
end);
(Rest, Expectation) ->
Fun(Rest, Expectation)
end).
-spec params(binary(), fun()) -> any().
params(Data, Fun) ->
params(Data, Fun, []).
-spec params(binary(), fun(), [{binary(), binary()}]) -> any().
params(Data, Fun, Acc) ->
whitespace(Data,
fun (<< $;, Rest/binary >>) ->
param(Rest,
fun (Rest2, Attr, Value) ->
params(Rest2, Fun, [{Attr, Value}|Acc])
end);
(Rest) ->
Fun(Rest, lists:reverse(Acc))
end).
-spec param(binary(), fun()) -> any().
param(Data, Fun) ->
whitespace(Data,
fun (Rest) ->
token_ci(Rest,
fun (_Rest2, <<>>) -> {error, badarg};
(<< $=, Rest2/binary >>, Attr) ->
word(Rest2,
fun (Rest3, Value) ->
Fun(Rest3, Attr, Value)
end);
(_Rest2, _Attr) -> {error, badarg}
end)
end).
%% While this may not be the most efficient date parsing we can do,
%% it should work fine for our purposes because all HTTP dates should
%% be sent as RFC1123 dates in HTTP/1.1.
-spec http_date(binary()) -> any().
http_date(Data) ->
case rfc1123_date(Data) of
{error, badarg} ->
case rfc850_date(Data) of
{error, badarg} ->
case asctime_date(Data) of
{error, badarg} ->
{error, badarg};
HTTPDate ->
HTTPDate
end;
HTTPDate ->
HTTPDate
end;
HTTPDate ->
HTTPDate
end.
-spec rfc1123_date(binary()) -> any().
rfc1123_date(Data) ->
wkday(Data,
fun (<< ", ", Rest/binary >>, _WkDay) ->
date1(Rest,
fun (<< " ", Rest2/binary >>, Date) ->
time(Rest2,
fun (<< " GMT", Rest3/binary >>, Time) ->
http_date_ret(Rest3, {Date, Time});
(_Any, _Time) ->
{error, badarg}
end);
(_Any, _Date) ->
{error, badarg}
end);
(_Any, _WkDay) ->
{error, badarg}
end).
-spec rfc850_date(binary()) -> any().
%% From the RFC:
%% HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
%% which appears to be more than 50 years in the future is in fact
%% in the past (this helps solve the "year 2000" problem).
rfc850_date(Data) ->
weekday(Data,
fun (<< ", ", Rest/binary >>, _WeekDay) ->
date2(Rest,
fun (<< " ", Rest2/binary >>, Date) ->
time(Rest2,
fun (<< " GMT", Rest3/binary >>, Time) ->
http_date_ret(Rest3, {Date, Time});
(_Any, _Time) ->
{error, badarg}
end);
(_Any, _Date) ->
{error, badarg}
end);
(_Any, _WeekDay) ->
{error, badarg}
end).
-spec asctime_date(binary()) -> any().
asctime_date(Data) ->
wkday(Data,
fun (<< " ", Rest/binary >>, _WkDay) ->
date3(Rest,
fun (<< " ", Rest2/binary >>, PartialDate) ->
time(Rest2,
fun (<< " ", Rest3/binary >>, Time) ->
asctime_year(Rest3,
PartialDate, Time);
(_Any, _Time) ->
{error, badarg}
end);
(_Any, _PartialDate) ->
{error, badarg}
end);
(_Any, _WkDay) ->
{error, badarg}
end).
-spec asctime_year(binary(), tuple(), tuple()) -> any().
asctime_year(<< Y1, Y2, Y3, Y4, Rest/binary >>, {Month, Day}, Time)
when Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
Year = (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
http_date_ret(Rest, {{Year, Month, Day}, Time}).
-spec http_date_ret(binary(), tuple()) -> any().
http_date_ret(Data, DateTime = {Date, _Time}) ->
whitespace(Data,
fun (<<>>) ->
case calendar:valid_date(Date) of
true -> DateTime;
false -> {error, badarg}
end;
(_Any) ->
{error, badarg}
end).
%% We never use it, pretty much just checks the wkday is right.
-spec wkday(binary(), fun()) -> any().
wkday(<< WkDay:3/binary, Rest/binary >>, Fun)
when WkDay =:= <<"Mon">>; WkDay =:= <<"Tue">>; WkDay =:= <<"Wed">>;
WkDay =:= <<"Thu">>; WkDay =:= <<"Fri">>; WkDay =:= <<"Sat">>;
WkDay =:= <<"Sun">> ->
Fun(Rest, WkDay);
wkday(_Any, _Fun) ->
{error, badarg}.
%% We never use it, pretty much just checks the weekday is right.
-spec weekday(binary(), fun()) -> any().
weekday(<< "Monday", Rest/binary >>, Fun) ->
Fun(Rest, <<"Monday">>);
weekday(<< "Tuesday", Rest/binary >>, Fun) ->
Fun(Rest, <<"Tuesday">>);
weekday(<< "Wednesday", Rest/binary >>, Fun) ->
Fun(Rest, <<"Wednesday">>);
weekday(<< "Thursday", Rest/binary >>, Fun) ->
Fun(Rest, <<"Thursday">>);
weekday(<< "Friday", Rest/binary >>, Fun) ->
Fun(Rest, <<"Friday">>);
weekday(<< "Saturday", Rest/binary >>, Fun) ->
Fun(Rest, <<"Saturday">>);
weekday(<< "Sunday", Rest/binary >>, Fun) ->
Fun(Rest, <<"Sunday">>);
weekday(_Any, _Fun) ->
{error, badarg}.
-spec date1(binary(), fun()) -> any().
date1(<< D1, D2, " ", M:3/binary, " ", Y1, Y2, Y3, Y4, Rest/binary >>, Fun)
when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
case month(M) of
{error, badarg} ->
{error, badarg};
Month ->
Fun(Rest, {
(Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
Month,
(D1 - $0) * 10 + (D2 - $0)
})
end;
date1(_Data, _Fun) ->
{error, badarg}.
-spec date2(binary(), fun()) -> any().
date2(<< D1, D2, "-", M:3/binary, "-", Y1, Y2, Rest/binary >>, Fun)
when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9 ->
case month(M) of
{error, badarg} ->
{error, badarg};
Month ->
Year = (Y1 - $0) * 10 + (Y2 - $0),
Year2 = case Year > 50 of
true -> Year + 1900;
false -> Year + 2000
end,
Fun(Rest, {
Year2,
Month,
(D1 - $0) * 10 + (D2 - $0)
})
end;
date2(_Data, _Fun) ->
{error, badarg}.
-spec date3(binary(), fun()) -> any().
date3(<< M:3/binary, " ", D1, D2, Rest/binary >>, Fun)
when (D1 >= $0 andalso D1 =< $3) orelse D1 =:= $\s,
D2 >= $0, D2 =< $9 ->
case month(M) of
{error, badarg} ->
{error, badarg};
Month ->
Day = case D1 of
$\s -> D2 - $0;
D1 -> (D1 - $0) * 10 + (D2 - $0)
end,
Fun(Rest, {Month, Day})
end;
date3(_Data, _Fun) ->
{error, badarg}.
-spec month(<< _:24 >>) -> 1..12 | {error, badarg}.
month(<<"Jan">>) -> 1;
month(<<"Feb">>) -> 2;
month(<<"Mar">>) -> 3;
month(<<"Apr">>) -> 4;
month(<<"May">>) -> 5;
month(<<"Jun">>) -> 6;
month(<<"Jul">>) -> 7;
month(<<"Aug">>) -> 8;
month(<<"Sep">>) -> 9;
month(<<"Oct">>) -> 10;
month(<<"Nov">>) -> 11;
month(<<"Dec">>) -> 12;
month(_Any) -> {error, badarg}.
-spec time(binary(), fun()) -> any().
time(<< H1, H2, ":", M1, M2, ":", S1, S2, Rest/binary >>, Fun)
when H1 >= $0, H1 =< $2, H2 >= $0, H2 =< $9,
M1 >= $0, M1 =< $5, M2 >= $0, M2 =< $9,
S1 >= $0, S1 =< $5, S2 >= $0, S2 =< $9 ->
Hour = (H1 - $0) * 10 + (H2 - $0),
case Hour < 24 of
true ->
Time = {
Hour,
(M1 - $0) * 10 + (M2 - $0),
(S1 - $0) * 10 + (S2 - $0)
},
Fun(Rest, Time);
false ->
{error, badarg}
end.
-spec whitespace(binary(), fun()) -> any().
whitespace(<< C, Rest/binary >>, Fun)
when C =:= $\s; C =:= $\t ->
whitespace(Rest, Fun);
whitespace(Data, Fun) ->
Fun(Data).
-spec digits(binary()) -> non_neg_integer() | {error, badarg}.
digits(Data) ->
digits(Data,
fun (Rest, I) ->
whitespace(Rest,
fun (<<>>) ->
I;
(_Rest2) ->
{error, badarg}
end)
end).
-spec digits(binary(), fun()) -> any().
digits(<< C, Rest/binary >>, Fun)
when C >= $0, C =< $9 ->
digits(Rest, Fun, C - $0);
digits(_Data, _Fun) ->
{error, badarg}.
-spec digits(binary(), fun(), non_neg_integer()) -> any().
digits(<< C, Rest/binary >>, Fun, Acc)
when C >= $0, C =< $9 ->
digits(Rest, Fun, Acc * 10 + (C - $0));
digits(Data, Fun, Acc) ->
Fun(Data, Acc).
%% Changes all characters to lowercase.
-spec alpha(binary(), fun()) -> any().
alpha(Data, Fun) ->
alpha(Data, Fun, <<>>).
-spec alpha(binary(), fun(), binary()) -> any().
alpha(<<>>, Fun, Acc) ->
Fun(<<>>, Acc);
alpha(<< C, Rest/binary >>, Fun, Acc)
when C >= $a andalso C =< $z;
C >= $A andalso C =< $Z ->
C2 = cowboy_bstr:char_to_lower(C),
alpha(Rest, Fun, << Acc/binary, C2 >>);
alpha(Data, Fun, Acc) ->
Fun(Data, Acc).
-spec alphanumeric(binary(), fun()) -> any().
alphanumeric(Data, Fun) ->
alphanumeric(Data, Fun, <<>>).
-spec alphanumeric(binary(), fun(), binary()) -> any().
alphanumeric(<<>>, Fun, Acc) ->
Fun(<<>>, Acc);
alphanumeric(<< C, Rest/binary >>, Fun, Acc)
when C >= $a andalso C =< $z;
C >= $A andalso C =< $Z;
C >= $0 andalso C =< $9 ->
C2 = cowboy_bstr:char_to_lower(C),
alphanumeric(Rest, Fun, << Acc/binary, C2 >>);
alphanumeric(Data, Fun, Acc) ->
Fun(Data, Acc).
%% @doc Parse either a token or a quoted string.
-spec word(binary(), fun()) -> any().
word(Data = << $", _/binary >>, Fun) ->
quoted_string(Data, Fun);
word(Data, Fun) ->
token(Data,
fun (_Rest, <<>>) -> {error, badarg};
(Rest, Token) -> Fun(Rest, Token)
end).
%% Changes all characters to lowercase.
-spec token_ci(binary(), fun()) -> any().
token_ci(Data, Fun) ->
token(Data, Fun, ci, <<>>).
-spec token(binary(), fun()) -> any().
token(Data, Fun) ->
token(Data, Fun, cs, <<>>).
-spec token(binary(), fun(), ci | cs, binary()) -> any().
token(<<>>, Fun, _Case, Acc) ->
Fun(<<>>, Acc);
token(Data = << C, _Rest/binary >>, Fun, _Case, Acc)
when C =:= $(; C =:= $); C =:= $<; C =:= $>; C =:= $@;
C =:= $,; C =:= $;; C =:= $:; C =:= $\\; C =:= $";
C =:= $/; C =:= $[; C =:= $]; C =:= $?; C =:= $=;
C =:= ${; C =:= $}; C =:= $\s; C =:= $\t;
C < 32; C =:= 127 ->
Fun(Data, Acc);
token(<< C, Rest/binary >>, Fun, Case = ci, Acc) ->
C2 = cowboy_bstr:char_to_lower(C),
token(Rest, Fun, Case, << Acc/binary, C2 >>);
token(<< C, Rest/binary >>, Fun, Case, Acc) ->
token(Rest, Fun, Case, << Acc/binary, C >>).
-spec quoted_string(binary(), fun()) -> any().
quoted_string(<< $", Rest/binary >>, Fun) ->
quoted_string(Rest, Fun, <<>>);
quoted_string(_, _Fun) ->
{error, badarg}.
-spec quoted_string(binary(), fun(), binary()) -> any().
quoted_string(<<>>, _Fun, _Acc) ->
{error, badarg};
quoted_string(<< $", Rest/binary >>, Fun, Acc) ->
Fun(Rest, Acc);
quoted_string(<< $\\, C, Rest/binary >>, Fun, Acc) ->
quoted_string(Rest, Fun, << Acc/binary, C >>);
quoted_string(<< C, Rest/binary >>, Fun, Acc) ->
quoted_string(Rest, Fun, << Acc/binary, C >>).
-spec qvalue(binary(), fun()) -> any().
qvalue(<< $0, $., Rest/binary >>, Fun) ->
qvalue(Rest, Fun, 0, 100);
%% Some user agents use q=.x instead of q=0.x
qvalue(<< $., Rest/binary >>, Fun) ->
qvalue(Rest, Fun, 0, 100);
qvalue(<< $0, Rest/binary >>, Fun) ->
Fun(Rest, 0);
qvalue(<< $1, $., $0, $0, $0, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(<< $1, $., $0, $0, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(<< $1, $., $0, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(<< $1, Rest/binary >>, Fun) ->
Fun(Rest, 1000);
qvalue(_Data, _Fun) ->
{error, badarg}.
-spec qvalue(binary(), fun(), integer(), 1 | 10 | 100) -> any().
qvalue(Data, Fun, Q, 0) ->
Fun(Data, Q);
qvalue(<< C, Rest/binary >>, Fun, Q, M)
when C >= $0, C =< $9 ->
qvalue(Rest, Fun, Q + (C - $0) * M, M div 10);
qvalue(Data, Fun, Q, _M) ->
Fun(Data, Q).
%% Only RFC2617 Basic authorization is supported so far.
-spec authorization(binary(), binary()) -> {binary(), any()} | {error, badarg}.
authorization(UserPass, Type = <<"basic">>) ->
whitespace(UserPass,
fun(D) ->
authorization_basic_userid(base64:mime_decode(D),
fun(Rest, Userid) ->
authorization_basic_password(Rest,
fun(Password) ->
{Type, {Userid, Password}}
end)
end)
end);
authorization(String, Type) ->
whitespace(String, fun(Rest) -> {Type, Rest} end).
-spec authorization_basic_userid(binary(), fun()) -> any().
authorization_basic_userid(Data, Fun) ->
authorization_basic_userid(Data, Fun, <<>>).
authorization_basic_userid(<<>>, _Fun, _Acc) ->
{error, badarg};
authorization_basic_userid(<<C, _Rest/binary>>, _Fun, Acc)
when C < 32; C =:= 127; (C =:=$: andalso Acc =:= <<>>) ->
{error, badarg};
authorization_basic_userid(<<$:, Rest/binary>>, Fun, Acc) ->
Fun(Rest, Acc);
authorization_basic_userid(<<C, Rest/binary>>, Fun, Acc) ->
authorization_basic_userid(Rest, Fun, <<Acc/binary, C>>).
-spec authorization_basic_password(binary(), fun()) -> any().
authorization_basic_password(Data, Fun) ->
authorization_basic_password(Data, Fun, <<>>).
authorization_basic_password(<<C, _Rest/binary>>, _Fun, _Acc)
when C < 32; C=:= 127 ->
{error, badarg};
authorization_basic_password(<<>>, Fun, Acc) ->
Fun(Acc);
authorization_basic_password(<<C, Rest/binary>>, Fun, Acc) ->
authorization_basic_password(Rest, Fun, <<Acc/binary, C>>).
-spec range(binary()) -> {Unit, [Range]} | {error, badarg} when
Unit :: binary(),
Range :: {non_neg_integer(), non_neg_integer() | infinity} | neg_integer().
range(Data) ->
token_ci(Data, fun range/2).
range(Data, Token) ->
whitespace(Data,
fun(<<"=", Rest/binary>>) ->
case list(Rest, fun range_beginning/2) of
{error, badarg} ->
{error, badarg};
Ranges ->
{Token, Ranges}
end;
(_) ->
{error, badarg}
end).
range_beginning(Data, Fun) ->
range_digits(Data, suffix,
fun(D, RangeBeginning) ->
range_ending(D, Fun, RangeBeginning)
end).
range_ending(Data, Fun, RangeBeginning) ->
whitespace(Data,
fun(<<"-", R/binary>>) ->
case RangeBeginning of
suffix ->
range_digits(R, fun(D, RangeEnding) -> Fun(D, -RangeEnding) end);
_ ->
range_digits(R, infinity,
fun(D, RangeEnding) ->
Fun(D, {RangeBeginning, RangeEnding})
end)
end;
(_) ->
{error, badarg}
end).
-spec range_digits(binary(), fun()) -> any().
range_digits(Data, Fun) ->
whitespace(Data,
fun(D) ->
digits(D, Fun)
end).
-spec range_digits(binary(), any(), fun()) -> any().
range_digits(Data, Default, Fun) ->
whitespace(Data,
fun(<< C, Rest/binary >>) when C >= $0, C =< $9 ->
digits(Rest, Fun, C - $0);
(_) ->
Fun(Data, Default)
end).
-spec parameterized_tokens(binary()) -> any().
parameterized_tokens(Data) ->
nonempty_list(Data,
fun (D, Fun) ->
token(D,
fun (_Rest, <<>>) -> {error, badarg};
(Rest, Token) ->
parameterized_tokens_params(Rest,
fun (Rest2, Params) ->
Fun(Rest2, {Token, Params})
end, [])
end)
end).
-spec parameterized_tokens_params(binary(), fun(), [binary() | {binary(), binary()}]) -> any().
parameterized_tokens_params(Data, Fun, Acc) ->
whitespace(Data,
fun (<< $;, Rest/binary >>) ->
parameterized_tokens_param(Rest,
fun (Rest2, Param) ->
parameterized_tokens_params(Rest2, Fun, [Param|Acc])
end);
(Rest) ->
Fun(Rest, lists:reverse(Acc))
end).
-spec parameterized_tokens_param(binary(), fun()) -> any().
parameterized_tokens_param(Data, Fun) ->
whitespace(Data,
fun (Rest) ->
token(Rest,
fun (_Rest2, <<>>) -> {error, badarg};
(<< $=, Rest2/binary >>, Attr) ->
word(Rest2,
fun (Rest3, Value) ->
Fun(Rest3, {Attr, Value})
end);
(Rest2, Attr) ->
Fun(Rest2, Attr)
end)
end).
%% Decoding.
%% @todo Move this to cowlib too I suppose. :-)
-spec ce_identity(binary()) -> {ok, binary()}.
ce_identity(Data) ->
{ok, Data}.
%% Tests.
-ifdef(TEST).
nonempty_charset_list_test_() ->
Tests = [
{<<>>, {error, badarg}},
{<<"iso-8859-5, unicode-1-1;q=0.8">>, [
{<<"iso-8859-5">>, 1000},
{<<"unicode-1-1">>, 800}
]},
%% Some user agents send this invalid value for the Accept-Charset header
{<<"ISO-8859-1;utf-8;q=0.7,*;q=0.7">>, [
{<<"iso-8859-1">>, 1000},
{<<"utf-8">>, 700},
{<<"*">>, 700}
]}
],
[{V, fun() -> R = nonempty_list(V, fun conneg/2) end} || {V, R} <- Tests].
nonempty_language_range_list_test_() ->
Tests = [
{<<"da, en-gb;q=0.8, en;q=0.7">>, [
{<<"da">>, 1000},
{<<"en-gb">>, 800},
{<<"en">>, 700}
]},
{<<"en, en-US, en-cockney, i-cherokee, x-pig-latin, es-419">>, [
{<<"en">>, 1000},
{<<"en-us">>, 1000},
{<<"en-cockney">>, 1000},
{<<"i-cherokee">>, 1000},
{<<"x-pig-latin">>, 1000},
{<<"es-419">>, 1000}
]}
],
[{V, fun() -> R = nonempty_list(V, fun language_range/2) end}
|| {V, R} <- Tests].
nonempty_token_list_test_() ->
Tests = [
{<<>>, {error, badarg}},
{<<" ">>, {error, badarg}},
{<<" , ">>, {error, badarg}},
{<<",,,">>, {error, badarg}},
{<<"a b">>, {error, badarg}},
{<<"a , , , ">>, [<<"a">>]},
{<<" , , , a">>, [<<"a">>]},
{<<"a, , b">>, [<<"a">>, <<"b">>]},
{<<"close">>, [<<"close">>]},
{<<"keep-alive, upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
],
[{V, fun() -> R = nonempty_list(V, fun token/2) end} || {V, R} <- Tests].
media_range_list_test_() ->
Tests = [
{<<"audio/*; q=0.2, audio/basic">>, [
{{<<"audio">>, <<"*">>, []}, 200, []},
{{<<"audio">>, <<"basic">>, []}, 1000, []}
]},
{<<"text/plain; q=0.5, text/html, "
"text/x-dvi; q=0.8, text/x-c">>, [
{{<<"text">>, <<"plain">>, []}, 500, []},
{{<<"text">>, <<"html">>, []}, 1000, []},
{{<<"text">>, <<"x-dvi">>, []}, 800, []},
{{<<"text">>, <<"x-c">>, []}, 1000, []}
]},
{<<"text/*, text/html, text/html;level=1, */*">>, [
{{<<"text">>, <<"*">>, []}, 1000, []},
{{<<"text">>, <<"html">>, []}, 1000, []},
{{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
{{<<"*">>, <<"*">>, []}, 1000, []}
]},
{<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
"text/html;level=2;q=0.4, */*;q=0.5">>, [
{{<<"text">>, <<"*">>, []}, 300, []},
{{<<"text">>, <<"html">>, []}, 700, []},
{{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
{{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []},
{{<<"*">>, <<"*">>, []}, 500, []}
]},
{<<"text/html;level=1;quoted=\"hi hi hi\";"
"q=0.123;standalone;complex=gits, text/plain">>, [
{{<<"text">>, <<"html">>,
[{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123,
[<<"standalone">>, {<<"complex">>, <<"gits">>}]},
{{<<"text">>, <<"plain">>, []}, 1000, []}
]},
{<<"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2">>, [
{{<<"text">>, <<"html">>, []}, 1000, []},
{{<<"image">>, <<"gif">>, []}, 1000, []},
{{<<"image">>, <<"jpeg">>, []}, 1000, []},
{{<<"*">>, <<"*">>, []}, 200, []},
{{<<"*">>, <<"*">>, []}, 200, []}
]}
],
[{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests].
entity_tag_match_test_() ->
Tests = [
{<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
{<<"\"xyzzy\", W/\"r2d2xxxx\", \"c3piozzzz\"">>,
[{strong, <<"xyzzy">>},
{weak, <<"r2d2xxxx">>},
{strong, <<"c3piozzzz">>}]},
{<<"*">>, '*'}
],
[{V, fun() -> R = entity_tag_match(V) end} || {V, R} <- Tests].
http_date_test_() ->
Tests = [
{<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
{<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
{<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
],
[{V, fun() -> R = http_date(V) end} || {V, R} <- Tests].
rfc1123_date_test_() ->
Tests = [
{<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
],
[{V, fun() -> R = rfc1123_date(V) end} || {V, R} <- Tests].
rfc850_date_test_() ->
Tests = [
{<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
],
[{V, fun() -> R = rfc850_date(V) end} || {V, R} <- Tests].
asctime_date_test_() ->
Tests = [
{<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
],
[{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests].
content_type_test_() ->
Tests = [
{<<"text/plain; charset=iso-8859-4">>,
{<<"text">>, <<"plain">>, [{<<"charset">>, <<"iso-8859-4">>}]}},
{<<"multipart/form-data \t;Boundary=\"MultipartIsUgly\"">>,
{<<"multipart">>, <<"form-data">>, [
{<<"boundary">>, <<"MultipartIsUgly">>}
]}},
{<<"foo/bar; one=FirstParam; two=SecondParam">>,
{<<"foo">>, <<"bar">>, [
{<<"one">>, <<"FirstParam">>},
{<<"two">>, <<"SecondParam">>}
]}}
],
[{V, fun () -> R = content_type(V) end} || {V, R} <- Tests].
parameterized_tokens_test_() ->
Tests = [
{<<"foo">>, [{<<"foo">>, []}]},
{<<"bar; baz=2">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}]}]},
{<<"bar; baz=2;bat">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}, <<"bat">>]}]},
{<<"bar; baz=2;bat=\"z=1,2;3\"">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}, {<<"bat">>, <<"z=1,2;3">>}]}]},
{<<"foo, bar; baz=2">>, [{<<"foo">>, []}, {<<"bar">>, [{<<"baz">>, <<"2">>}]}]}
],
[{V, fun () -> R = parameterized_tokens(V) end} || {V, R} <- Tests].
digits_test_() ->
Tests = [
{<<"42 ">>, 42},
{<<"69\t">>, 69},
{<<"1337">>, 1337}
],
[{V, fun() -> R = digits(V) end} || {V, R} <- Tests].
http_authorization_test_() ->
Tests = [
{<<"basic">>, <<"QWxsYWRpbjpvcGVuIHNlc2FtZQ==">>,
{<<"basic">>, {<<"Alladin">>, <<"open sesame">>}}},
{<<"basic">>, <<"dXNlcm5hbWU6">>,
{<<"basic">>, {<<"username">>, <<>>}}},
{<<"basic">>, <<"dXNlcm5hbWUK">>,
{error, badarg}},
{<<"basic">>, <<"_[]@#$%^&*()-AA==">>,
{error, badarg}},
{<<"basic">>, <<"dXNlcjpwYXNzCA==">>,
{error, badarg}},
{<<"bearer">>, <<" some_secret_key">>,
{<<"bearer">>,<<"some_secret_key">>}}
],
[{V, fun() -> R = authorization(V,T) end} || {T, V, R} <- Tests].
http_range_test_() ->
Tests = [
{<<"bytes=1-20">>,
{<<"bytes">>, [{1, 20}]}},
{<<"bytes=-100">>,
{<<"bytes">>, [-100]}},
{<<"bytes=1-">>,
{<<"bytes">>, [{1, infinity}]}},
{<<"bytes=1-20,30-40,50-">>,
{<<"bytes">>, [{1, 20}, {30, 40}, {50, infinity}]}},
{<<"bytes = 1 - 20 , 50 - , - 300 ">>,
{<<"bytes">>, [{1, 20}, {50, infinity}, -300]}},
{<<"bytes=1-20,-500,30-40">>,
{<<"bytes">>, [{1, 20}, -500, {30, 40}]}},
{<<"test=1-20,-500,30-40">>,
{<<"test">>, [{1, 20}, -500, {30, 40}]}},
{<<"bytes=-">>,
{error, badarg}},
{<<"bytes=-30,-">>,
{error, badarg}}
],
[fun() -> R = range(V) end ||{V, R} <- Tests].
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_http_handler).
-type opts() :: any().
-type state() :: any().
-type terminate_reason() :: {normal, shutdown}
| {normal, timeout} %% Only occurs in loop handlers.
| {error, closed} %% Only occurs in loop handlers.
| {error, overflow} %% Only occurs in loop handlers.
| {error, atom()}.
-callback init({atom(), http}, Req, opts())
-> {ok, Req, state()}
| {loop, Req, state()}
| {loop, Req, state(), hibernate}
| {loop, Req, state(), timeout()}
| {loop, Req, state(), timeout(), hibernate}
| {shutdown, Req, state()}
| {upgrade, protocol, module()}
| {upgrade, protocol, module(), Req, opts()}
when Req::cowboy_req:req().
-callback handle(Req, State) -> {ok, Req, State}
when Req::cowboy_req:req(), State::state().
-callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_loop_handler).
-type opts() :: any().
-type state() :: any().
-type terminate_reason() :: {normal, shutdown}
| {normal, timeout}
| {error, closed}
| {error, overflow}
| {error, atom()}.
-callback init({atom(), http}, Req, opts())
-> {ok, Req, state()}
| {loop, Req, state()}
| {loop, Req, state(), hibernate}
| {loop, Req, state(), timeout()}
| {loop, Req, state(), timeout(), hibernate}
| {shutdown, Req, state()}
| {upgrade, protocol, module()}
| {upgrade, protocol, module(), Req, opts()}
when Req::cowboy_req:req().
-callback info(any(), Req, State)
-> {ok, Req, State}
| {loop, Req, State}
| {loop, Req, State, hibernate}
when Req::cowboy_req:req(), State::state().
-callback terminate(terminate_reason(), cowboy_req:req(), state()) -> ok.
|
> > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_middleware).
-type env() :: [{atom(), any()}].
-export_type([env/0]).
-callback execute(Req, Env)
-> {ok, Req, Env}
| {suspend, module(), atom(), [any()]}
| {halt, Req}
| {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::env().
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_protocol).
%% API.
-export([start_link/4]).
%% Internal.
-export([init/4]).
-export([parse_request/3]).
-export([resume/6]).
-type opts() :: [{compress, boolean()}
| {env, cowboy_middleware:env()}
| {max_empty_lines, non_neg_integer()}
| {max_header_name_length, non_neg_integer()}
| {max_header_value_length, non_neg_integer()}
| {max_headers, non_neg_integer()}
| {max_keepalive, non_neg_integer()}
| {max_request_line_length, non_neg_integer()}
| {middlewares, [module()]}
| {onrequest, cowboy:onrequest_fun()}
| {onresponse, cowboy:onresponse_fun()}
| {timeout, timeout()}].
-export_type([opts/0]).
-record(state, {
socket :: inet:socket(),
transport :: module(),
middlewares :: [module()],
compress :: boolean(),
env :: cowboy_middleware:env(),
onrequest :: undefined | cowboy:onrequest_fun(),
onresponse = undefined :: undefined | cowboy:onresponse_fun(),
max_empty_lines :: non_neg_integer(),
req_keepalive = 1 :: non_neg_integer(),
max_keepalive :: non_neg_integer(),
max_request_line_length :: non_neg_integer(),
max_header_name_length :: non_neg_integer(),
max_header_value_length :: non_neg_integer(),
max_headers :: non_neg_integer(),
timeout :: timeout(),
until :: non_neg_integer() | infinity
}).
-include_lib("cowlib/include/cow_inline.hrl").
%% API.
-spec start_link(ranch:ref(), inet:socket(), module(), opts()) -> {ok, pid()}.
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.
%% Internal.
%% Faster alternative to proplists:get_value/3.
get_value(Key, Opts, Default) ->
case lists:keyfind(Key, 1, Opts) of
{_, Value} -> Value;
_ -> Default
end.
-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, Opts) ->
Compress = get_value(compress, Opts, false),
MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
MaxHeaderValueLength = get_value(max_header_value_length, Opts, 4096),
MaxHeaders = get_value(max_headers, Opts, 100),
MaxKeepalive = get_value(max_keepalive, Opts, 100),
MaxRequestLineLength = get_value(max_request_line_length, Opts, 4096),
Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
Env = [{listener, Ref}|get_value(env, Opts, [])],
OnRequest = get_value(onrequest, Opts, undefined),
OnResponse = get_value(onresponse, Opts, undefined),
Timeout = get_value(timeout, Opts, 5000),
ok = ranch:accept_ack(Ref),
wait_request(<<>>, #state{socket=Socket, transport=Transport,
middlewares=Middlewares, compress=Compress, env=Env,
max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
max_request_line_length=MaxRequestLineLength,
max_header_name_length=MaxHeaderNameLength,
max_header_value_length=MaxHeaderValueLength, max_headers=MaxHeaders,
onrequest=OnRequest, onresponse=OnResponse,
timeout=Timeout, until=until(Timeout)}, 0).
-spec until(timeout()) -> non_neg_integer() | infinity.
until(infinity) ->
infinity;
until(Timeout) ->
{Me, S, Mi} = os:timestamp(),
Me * 1000000000 + S * 1000 + Mi div 1000 + Timeout.
%% Request parsing.
%%
%% The next set of functions is the request parsing code. All of it
%% runs using a single binary match context. This optimization ends
%% right after the header parsing is finished and the code becomes
%% more interesting past that point.
-spec recv(inet:socket(), module(), non_neg_integer() | infinity)
-> {ok, binary()} | {error, closed | timeout | atom()}.
recv(Socket, Transport, infinity) ->
Transport:recv(Socket, 0, infinity);
recv(Socket, Transport, Until) ->
{Me, S, Mi} = os:timestamp(),
Now = Me * 1000000000 + S * 1000 + Mi div 1000,
Timeout = Until - Now,
if Timeout < 0 ->
{error, timeout};
true ->
Transport:recv(Socket, 0, Timeout)
end.
-spec wait_request(binary(), #state{}, non_neg_integer()) -> ok.
wait_request(Buffer, State=#state{socket=Socket, transport=Transport,
until=Until}, ReqEmpty) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_request(<< Buffer/binary, Data/binary >>, State, ReqEmpty);
{error, _} ->
terminate(State)
end.
-spec parse_request(binary(), #state{}, non_neg_integer()) -> ok.
%% Empty lines must be using \r\n.
parse_request(<< $\n, _/binary >>, State, _) ->
error_terminate(400, State);
%% We limit the length of the Request-line to MaxLength to avoid endlessly
%% reading from the socket and eventually crashing.
parse_request(Buffer, State=#state{max_request_line_length=MaxLength,
max_empty_lines=MaxEmpty}, ReqEmpty) ->
case match_eol(Buffer, 0) of
nomatch when byte_size(Buffer) > MaxLength ->
error_terminate(414, State);
nomatch ->
wait_request(Buffer, State, ReqEmpty);
1 when ReqEmpty =:= MaxEmpty ->
error_terminate(400, State);
1 ->
<< _:16, Rest/binary >> = Buffer,
parse_request(Rest, State, ReqEmpty + 1);
_ ->
parse_method(Buffer, State, <<>>)
end.
match_eol(<< $\n, _/bits >>, N) ->
N;
match_eol(<< _, Rest/bits >>, N) ->
match_eol(Rest, N + 1);
match_eol(_, _) ->
nomatch.
parse_method(<< C, Rest/bits >>, State, SoFar) ->
case C of
$\r -> error_terminate(400, State);
$\s -> parse_uri(Rest, State, SoFar);
_ -> parse_method(Rest, State, << SoFar/binary, C >>)
end.
parse_uri(<< $\r, _/bits >>, State, _) ->
error_terminate(400, State);
parse_uri(<< "* ", Rest/bits >>, State, Method) ->
parse_version(Rest, State, Method, <<"*">>, <<>>);
parse_uri(<< "http://", Rest/bits >>, State, Method) ->
parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "https://", Rest/bits >>, State, Method) ->
parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "HTTP://", Rest/bits >>, State, Method) ->
parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "HTTPS://", Rest/bits >>, State, Method) ->
parse_uri_skip_host(Rest, State, Method);
parse_uri(Buffer, State, Method) ->
parse_uri_path(Buffer, State, Method, <<>>).
parse_uri_skip_host(<< C, Rest/bits >>, State, Method) ->
case C of
$\r -> error_terminate(400, State);
$/ -> parse_uri_path(Rest, State, Method, <<"/">>);
$\s -> parse_version(Rest, State, Method, <<"/">>, <<>>);
$? -> parse_uri_query(Rest, State, Method, <<"/">>, <<>>);
$# -> skip_uri_fragment(Rest, State, Method, <<"/">>, <<>>);
_ -> parse_uri_skip_host(Rest, State, Method)
end.
parse_uri_path(<< C, Rest/bits >>, State, Method, SoFar) ->
case C of
$\r -> error_terminate(400, State);
$\s -> parse_version(Rest, State, Method, SoFar, <<>>);
$? -> parse_uri_query(Rest, State, Method, SoFar, <<>>);
$# -> skip_uri_fragment(Rest, State, Method, SoFar, <<>>);
_ -> parse_uri_path(Rest, State, Method, << SoFar/binary, C >>)
end.
parse_uri_query(<< C, Rest/bits >>, S, M, P, SoFar) ->
case C of
$\r -> error_terminate(400, S);
$\s -> parse_version(Rest, S, M, P, SoFar);
$# -> skip_uri_fragment(Rest, S, M, P, SoFar);
_ -> parse_uri_query(Rest, S, M, P, << SoFar/binary, C >>)
end.
skip_uri_fragment(<< C, Rest/bits >>, S, M, P, Q) ->
case C of
$\r -> error_terminate(400, S);
$\s -> parse_version(Rest, S, M, P, Q);
_ -> skip_uri_fragment(Rest, S, M, P, Q)
end.
parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q) ->
parse_header(Rest, S, M, P, Q, 'HTTP/1.1', []);
parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q) ->
parse_header(Rest, S, M, P, Q, 'HTTP/1.0', []);
parse_version(_, State, _, _, _) ->
error_terminate(505, State).
%% Stop receiving data if we have more than allowed number of headers.
wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, Headers)
when length(Headers) >= MaxHeaders ->
error_terminate(400, State);
wait_header(Buffer, State=#state{socket=Socket, transport=Transport,
until=Until}, M, P, Q, V, H) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_header(<< Buffer/binary, Data/binary >>,
State, M, P, Q, V, H);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, V, Headers) ->
request(Rest, S, M, P, Q, V, lists:reverse(Headers));
parse_header(Buffer, State=#state{max_header_name_length=MaxLength},
M, P, Q, V, H) ->
case match_colon(Buffer, 0) of
nomatch when byte_size(Buffer) > MaxLength ->
error_terminate(400, State);
nomatch ->
wait_header(Buffer, State, M, P, Q, V, H);
_ ->
parse_hd_name(Buffer, State, M, P, Q, V, H, <<>>)
end.
match_colon(<< $:, _/bits >>, N) ->
N;
match_colon(<< _, Rest/bits >>, N) ->
match_colon(Rest, N + 1);
match_colon(_, _) ->
nomatch.
parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, V, H, SoFar) ->
case C of
$: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, SoFar);
$\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
$\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
?INLINE_LOWERCASE(parse_hd_name, Rest, S, M, P, Q, V, H, SoFar)
end.
parse_hd_name_ws(<< C, Rest/bits >>, S, M, P, Q, V, H, Name) ->
case C of
$\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name);
$\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name);
$: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, Name)
end.
wait_hd_before_value(Buffer, State=#state{
socket=Socket, transport=Transport, until=Until},
M, P, Q, V, H, N) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_hd_before_value(<< Buffer/binary, Data/binary >>,
State, M, P, Q, V, H, N);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
parse_hd_before_value(<< $\s, Rest/bits >>, S, M, P, Q, V, H, N) ->
parse_hd_before_value(Rest, S, M, P, Q, V, H, N);
parse_hd_before_value(<< $\t, Rest/bits >>, S, M, P, Q, V, H, N) ->
parse_hd_before_value(Rest, S, M, P, Q, V, H, N);
parse_hd_before_value(Buffer, State=#state{
max_header_value_length=MaxLength}, M, P, Q, V, H, N) ->
case match_eol(Buffer, 0) of
nomatch when byte_size(Buffer) > MaxLength ->
error_terminate(400, State);
nomatch ->
wait_hd_before_value(Buffer, State, M, P, Q, V, H, N);
_ ->
parse_hd_value(Buffer, State, M, P, Q, V, H, N, <<>>)
end.
%% We completely ignore the first argument which is always
%% the empty binary. We keep it there because we don't want
%% to change the other arguments' position and trigger costy
%% operations for no reasons.
wait_hd_value(_, State=#state{
socket=Socket, transport=Transport, until=Until},
M, P, Q, V, H, N, SoFar) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_hd_value(Data, State, M, P, Q, V, H, N, SoFar);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
%% Pushing back as much as we could the retrieval of new data
%% to check for multilines allows us to avoid a few tests in
%% the critical path, but forces us to have a special function.
wait_hd_value_nl(_, State=#state{
socket=Socket, transport=Transport, until=Until},
M, P, Q, V, Headers, Name, SoFar) ->
case recv(Socket, Transport, Until) of
{ok, << C, Data/bits >>} when C =:= $\s; C =:= $\t ->
parse_hd_value(Data, State, M, P, Q, V, Headers, Name, SoFar);
{ok, Data} ->
parse_header(Data, State, M, P, Q, V, [{Name, SoFar}|Headers]);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
parse_hd_value(<< $\r, Rest/bits >>, S, M, P, Q, V, Headers, Name, SoFar) ->
case Rest of
<< $\n >> ->
wait_hd_value_nl(<<>>, S, M, P, Q, V, Headers, Name, SoFar);
<< $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
parse_hd_value(Rest2, S, M, P, Q, V, Headers, Name,
<< SoFar/binary, C >>);
<< $\n, Rest2/bits >> ->
parse_header(Rest2, S, M, P, Q, V, [{Name, SoFar}|Headers])
end;
parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, V, H, N, SoFar) ->
parse_hd_value(Rest, S, M, P, Q, V, H, N, << SoFar/binary, C >>);
parse_hd_value(<<>>, State=#state{max_header_value_length=MaxLength},
_, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength ->
error_terminate(400, State);
parse_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar) ->
wait_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar).
request(B, State=#state{transport=Transport}, M, P, Q, Version, Headers) ->
case lists:keyfind(<<"host">>, 1, Headers) of
false when Version =:= 'HTTP/1.1' ->
error_terminate(400, State);
false ->
request(B, State, M, P, Q, Version, Headers,
<<>>, default_port(Transport:name()));
{_, RawHost} ->
try parse_host(RawHost, false, <<>>) of
{Host, undefined} ->
request(B, State, M, P, Q, Version, Headers,
Host, default_port(Transport:name()));
{Host, Port} ->
request(B, State, M, P, Q, Version, Headers,
Host, Port)
catch _:_ ->
error_terminate(400, State)
end
end.
-spec default_port(atom()) -> 80 | 443.
default_port(ssl) -> 443;
default_port(_) -> 80.
%% Same code as cow_http:parse_fullhost/1, but inline because we
%% really want this to go fast.
parse_host(<< $[, Rest/bits >>, false, <<>>) ->
parse_host(Rest, true, << $[ >>);
parse_host(<<>>, false, Acc) ->
{Acc, undefined};
parse_host(<< $:, Rest/bits >>, false, Acc) ->
{Acc, list_to_integer(binary_to_list(Rest))};
parse_host(<< $], Rest/bits >>, true, Acc) ->
parse_host(Rest, false, << Acc/binary, $] >>);
parse_host(<< C, Rest/bits >>, E, Acc) ->
case C of
?INLINE_LOWERCASE(parse_host, Rest, E, Acc)
end.
%% End of request parsing.
%%
%% We create the Req object and start handling the request.
request(Buffer, State=#state{socket=Socket, transport=Transport,
req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive,
compress=Compress, onresponse=OnResponse},
Method, Path, Query, Version, Headers, Host, Port) ->
case Transport:peername(Socket) of
{ok, Peer} ->
Req = cowboy_req:new(Socket, Transport, Peer, Method, Path,
Query, Version, Headers, Host, Port, Buffer,
ReqKeepalive < MaxKeepalive, Compress, OnResponse),
onrequest(Req, State);
{error, _} ->
%% Couldn't read the peer address; connection is gone.
terminate(State)
end.
%% Call the global onrequest callback. The callback can send a reply,
%% in which case we consider the request handled and move on to the next
%% one. Note that since we haven't dispatched yet, we don't know the
%% handler, host_info, path_info or bindings yet.
-spec onrequest(cowboy_req:req(), #state{}) -> ok.
onrequest(Req, State=#state{onrequest=undefined}) ->
execute(Req, State);
onrequest(Req, State=#state{onrequest=OnRequest}) ->
Req2 = OnRequest(Req),
case cowboy_req:get(resp_state, Req2) of
waiting -> execute(Req2, State);
_ -> next_request(Req2, State, ok)
end.
-spec execute(cowboy_req:req(), #state{}) -> ok.
execute(Req, State=#state{middlewares=Middlewares, env=Env}) ->
execute(Req, State, Env, Middlewares).
-spec execute(cowboy_req:req(), #state{}, cowboy_middleware:env(), [module()])
-> ok.
execute(Req, State, Env, []) ->
next_request(Req, State, get_value(result, Env, ok));
execute(Req, State, Env, [Middleware|Tail]) ->
case Middleware:execute(Req, Env) of
{ok, Req2, Env2} ->
execute(Req2, State, Env2, Tail);
{suspend, Module, Function, Args} ->
erlang:hibernate(?MODULE, resume,
[State, Env, Tail, Module, Function, Args]);
{halt, Req2} ->
next_request(Req2, State, ok);
{error, Code, Req2} ->
error_terminate(Code, Req2, State)
end.
-spec resume(#state{}, cowboy_middleware:env(), [module()],
module(), module(), [any()]) -> ok.
resume(State, Env, Tail, Module, Function, Args) ->
case apply(Module, Function, Args) of
{ok, Req2, Env2} ->
execute(Req2, State, Env2, Tail);
{suspend, Module2, Function2, Args2} ->
erlang:hibernate(?MODULE, resume,
[State, Env, Tail, Module2, Function2, Args2]);
{halt, Req2} ->
next_request(Req2, State, ok);
{error, Code, Req2} ->
error_terminate(Code, Req2, State)
end.
-spec next_request(cowboy_req:req(), #state{}, any()) -> ok.
next_request(Req, State=#state{req_keepalive=Keepalive, timeout=Timeout},
HandlerRes) ->
cowboy_req:ensure_response(Req, 204),
%% If we are going to close the connection,
%% we do not want to attempt to skip the body.
case cowboy_req:get(connection, Req) of
close ->
terminate(State);
_ ->
%% Skip the body if it is reasonably sized. Close otherwise.
Buffer = case cowboy_req:body(Req) of
{ok, _, Req2} -> cowboy_req:get(buffer, Req2);
_ -> close
end,
%% Flush the resp_sent message before moving on.
if HandlerRes =:= ok, Buffer =/= close ->
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
?MODULE:parse_request(Buffer,
State#state{req_keepalive=Keepalive + 1,
until=until(Timeout)}, 0);
true ->
terminate(State)
end
end.
-spec error_terminate(cowboy:http_status(), #state{}) -> ok.
error_terminate(Status, State=#state{socket=Socket, transport=Transport,
compress=Compress, onresponse=OnResponse}) ->
error_terminate(Status, cowboy_req:new(Socket, Transport,
undefined, <<"GET">>, <<>>, <<>>, 'HTTP/1.1', [], <<>>,
undefined, <<>>, false, Compress, OnResponse), State).
-spec error_terminate(cowboy:http_status(), cowboy_req:req(), #state{}) -> ok.
error_terminate(Status, Req, State) ->
_ = cowboy_req:reply(Status, Req),
terminate(State).
-spec terminate(#state{}) -> ok.
terminate(#state{socket=Socket, transport=Transport}) ->
Transport:close(Socket),
ok.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_req).
%% Request API.
-export([new/14]).
-export([method/1]).
-export([version/1]).
-export([peer/1]).
-export([host/1]).
-export([host_info/1]).
-export([port/1]).
-export([path/1]).
-export([path_info/1]).
-export([qs/1]).
-export([qs_val/2]).
-export([qs_val/3]).
-export([qs_vals/1]).
-export([host_url/1]).
-export([url/1]).
-export([binding/2]).
-export([binding/3]).
-export([bindings/1]).
-export([header/2]).
-export([header/3]).
-export([headers/1]).
-export([parse_header/2]).
-export([parse_header/3]).
-export([cookie/2]).
-export([cookie/3]).
-export([cookies/1]).
-export([meta/2]).
-export([meta/3]).
-export([set_meta/3]).
%% Request body API.
-export([has_body/1]).
-export([body_length/1]).
-export([body/1]).
-export([body/2]).
-export([body_qs/1]).
-export([body_qs/2]).
%% Multipart API.
-export([part/1]).
-export([part/2]).
-export([part_body/1]).
-export([part_body/2]).
%% Response API.
-export([set_resp_cookie/4]).
-export([set_resp_header/3]).
-export([set_resp_body/2]).
-export([set_resp_body_fun/2]).
-export([set_resp_body_fun/3]).
-export([has_resp_header/2]).
-export([has_resp_body/1]).
-export([delete_resp_header/2]).
-export([reply/2]).
-export([reply/3]).
-export([reply/4]).
-export([chunked_reply/2]).
-export([chunked_reply/3]).
-export([chunk/2]).
-export([upgrade_reply/3]).
-export([continue/1]).
-export([maybe_reply/2]).
-export([ensure_response/2]).
%% Private setter/getter API.
-export([append_buffer/2]).
-export([get/2]).
-export([set/2]).
-export([set_bindings/4]).
%% Misc API.
-export([compact/1]).
-export([lock/1]).
-export([to_list/1]).
-type cookie_opts() :: cow_cookie:cookie_opts().
-export_type([cookie_opts/0]).
-type content_decode_fun() :: fun((binary())
-> {ok, binary()}
| {error, atom()}).
-type transfer_decode_fun() :: fun((binary(), any())
-> cow_http_te:decode_ret()).
-type body_opts() :: [{continue, boolean()}
| {length, non_neg_integer()}
| {read_length, non_neg_integer()}
| {read_timeout, timeout()}
| {transfer_decode, transfer_decode_fun(), any()}
| {content_decode, content_decode_fun()}].
-export_type([body_opts/0]).
-type resp_body_fun() :: fun((any(), module()) -> ok).
-type send_chunk_fun() :: fun((iodata()) -> ok | {error, atom()}).
-type resp_chunked_fun() :: fun((send_chunk_fun()) -> ok).
-record(http_req, {
%% Transport.
socket = undefined :: any(),
transport = undefined :: undefined | module(),
connection = keepalive :: keepalive | close,
%% Request.
pid = undefined :: pid(),
method = <<"GET">> :: binary(),
version = 'HTTP/1.1' :: cowboy:http_version(),
peer = undefined :: undefined | {inet:ip_address(), inet:port_number()},
host = undefined :: undefined | binary(),
host_info = undefined :: undefined | cowboy_router:tokens(),
port = undefined :: undefined | inet:port_number(),
path = undefined :: binary(),
path_info = undefined :: undefined | cowboy_router:tokens(),
qs = undefined :: binary(),
qs_vals = undefined :: undefined | list({binary(), binary() | true}),
bindings = undefined :: undefined | cowboy_router:bindings(),
headers = [] :: cowboy:http_headers(),
p_headers = [] :: [any()],
cookies = undefined :: undefined | [{binary(), binary()}],
meta = [] :: [{atom(), any()}],
%% Request body.
body_state = waiting :: waiting | done | {stream, non_neg_integer(),
transfer_decode_fun(), any(), content_decode_fun()},
buffer = <<>> :: binary(),
multipart = undefined :: undefined | {binary(), binary()},
%% Response.
resp_compress = false :: boolean(),
resp_state = waiting :: locked | waiting | waiting_stream
| chunks | stream | done,
resp_headers = [] :: cowboy:http_headers(),
resp_body = <<>> :: iodata() | resp_body_fun()
| {non_neg_integer(), resp_body_fun()}
| {chunked, resp_chunked_fun()},
%% Functions.
onresponse = undefined :: undefined | already_called
| cowboy:onresponse_fun()
}).
-opaque req() :: #http_req{}.
-export_type([req/0]).
%% Request API.
-spec new(any(), module(),
undefined | {inet:ip_address(), inet:port_number()},
binary(), binary(), binary(),
cowboy:http_version(), cowboy:http_headers(), binary(),
inet:port_number() | undefined, binary(), boolean(), boolean(),
undefined | cowboy:onresponse_fun())
-> req().
new(Socket, Transport, Peer, Method, Path, Query,
Version, Headers, Host, Port, Buffer, CanKeepalive,
Compress, OnResponse) ->
Req = #http_req{socket=Socket, transport=Transport, pid=self(), peer=Peer,
method=Method, path=Path, qs=Query, version=Version,
headers=Headers, host=Host, port=Port, buffer=Buffer,
resp_compress=Compress, onresponse=OnResponse},
case CanKeepalive of
false ->
Req#http_req{connection=close};
true ->
case lists:keyfind(<<"connection">>, 1, Headers) of
false ->
case Version of
'HTTP/1.1' -> Req; %% keepalive
'HTTP/1.0' -> Req#http_req{connection=close}
end;
{_, ConnectionHeader} ->
Tokens = cow_http_hd:parse_connection(ConnectionHeader),
Connection = connection_to_atom(Tokens),
Req#http_req{connection=Connection,
p_headers=[{<<"connection">>, Tokens}]}
end
end.
-spec method(Req) -> {binary(), Req} when Req::req().
method(Req) ->
{Req#http_req.method, Req}.
-spec version(Req) -> {cowboy:http_version(), Req} when Req::req().
version(Req) ->
{Req#http_req.version, Req}.
-spec peer(Req)
-> {{inet:ip_address(), inet:port_number()}, Req}
when Req::req().
peer(Req) ->
{Req#http_req.peer, Req}.
-spec host(Req) -> {binary(), Req} when Req::req().
host(Req) ->
{Req#http_req.host, Req}.
-spec host_info(Req)
-> {cowboy_router:tokens() | undefined, Req} when Req::req().
host_info(Req) ->
{Req#http_req.host_info, Req}.
-spec port(Req) -> {inet:port_number(), Req} when Req::req().
port(Req) ->
{Req#http_req.port, Req}.
-spec path(Req) -> {binary(), Req} when Req::req().
path(Req) ->
{Req#http_req.path, Req}.
-spec path_info(Req)
-> {cowboy_router:tokens() | undefined, Req} when Req::req().
path_info(Req) ->
{Req#http_req.path_info, Req}.
-spec qs(Req) -> {binary(), Req} when Req::req().
qs(Req) ->
{Req#http_req.qs, Req}.
-spec qs_val(binary(), Req)
-> {binary() | true | undefined, Req} when Req::req().
qs_val(Name, Req) when is_binary(Name) ->
qs_val(Name, Req, undefined).
-spec qs_val(binary(), Req, Default)
-> {binary() | true | Default, Req} when Req::req(), Default::any().
qs_val(Name, Req=#http_req{qs=RawQs, qs_vals=undefined}, Default)
when is_binary(Name) ->
QsVals = cow_qs:parse_qs(RawQs),
qs_val(Name, Req#http_req{qs_vals=QsVals}, Default);
qs_val(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.qs_vals) of
{Name, Value} -> {Value, Req};
false -> {Default, Req}
end.
-spec qs_vals(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) ->
QsVals = cow_qs:parse_qs(RawQs),
qs_vals(Req#http_req{qs_vals=QsVals});
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
{QsVals, Req}.
%% The URL includes the scheme, host and port only.
-spec host_url(Req) -> {undefined | binary(), Req} when Req::req().
host_url(Req=#http_req{port=undefined}) ->
{undefined, Req};
host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) ->
TransportName = Transport:name(),
Secure = case TransportName of
ssl -> <<"s">>;
_ -> <<>>
end,
PortBin = case {TransportName, Port} of
{ssl, 443} -> <<>>;
{tcp, 80} -> <<>>;
_ -> << ":", (integer_to_binary(Port))/binary >>
end,
{<< "http", Secure/binary, "://", Host/binary, PortBin/binary >>, Req}.
%% The URL includes the scheme, host, port, path and query string.
-spec url(Req) -> {undefined | binary(), Req} when Req::req().
url(Req=#http_req{}) ->
{HostURL, Req2} = host_url(Req),
url(HostURL, Req2).
url(undefined, Req=#http_req{}) ->
{undefined, Req};
url(HostURL, Req=#http_req{path=Path, qs=QS}) ->
QS2 = case QS of
<<>> -> <<>>;
_ -> << "?", QS/binary >>
end,
{<< HostURL/binary, Path/binary, QS2/binary >>, Req}.
-spec binding(atom(), Req) -> {any() | undefined, Req} when Req::req().
binding(Name, Req) when is_atom(Name) ->
binding(Name, Req, undefined).
-spec binding(atom(), Req, Default)
-> {any() | Default, Req} when Req::req(), Default::any().
binding(Name, Req, Default) when is_atom(Name) ->
case lists:keyfind(Name, 1, Req#http_req.bindings) of
{Name, Value} -> {Value, Req};
false -> {Default, Req}
end.
-spec bindings(Req) -> {[{atom(), any()}], Req} when Req::req().
bindings(Req) ->
{Req#http_req.bindings, Req}.
-spec header(binary(), Req)
-> {binary() | undefined, Req} when Req::req().
header(Name, Req) ->
header(Name, Req, undefined).
-spec header(binary(), Req, Default)
-> {binary() | Default, Req} when Req::req(), Default::any().
header(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.headers) of
{Name, Value} -> {Value, Req};
false -> {Default, Req}
end.
-spec headers(Req) -> {cowboy:http_headers(), Req} when Req::req().
headers(Req) ->
{Req#http_req.headers, Req}.
-spec parse_header(binary(), Req)
-> {ok, any(), Req} | {undefined, binary(), Req}
| {error, badarg} when Req::req().
parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
case lists:keyfind(Name, 1, PHeaders) of
false -> parse_header(Name, Req, parse_header_default(Name));
{Name, Value} -> {ok, Value, Req}
end.
-spec parse_header_default(binary()) -> any().
parse_header_default(<<"transfer-encoding">>) -> [<<"identity">>];
parse_header_default(_Name) -> undefined.
-spec parse_header(binary(), Req, any())
-> {ok, any(), Req} | {undefined, binary(), Req}
| {error, badarg} when Req::req().
parse_header(Name = <<"accept">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:media_range/2)
end);
parse_header(Name = <<"accept-charset">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2)
end);
parse_header(Name = <<"accept-encoding">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:conneg/2)
end);
parse_header(Name = <<"accept-language">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
end);
parse_header(Name = <<"authorization">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:token_ci(Value, fun cowboy_http:authorization/2)
end);
parse_header(Name = <<"content-length">>, Req, Default) ->
parse_header(Name, Req, Default, fun cow_http_hd:parse_content_length/1);
parse_header(Name = <<"content-type">>, Req, Default) ->
parse_header(Name, Req, Default, fun cowboy_http:content_type/1);
parse_header(Name = <<"cookie">>, Req, Default) ->
parse_header(Name, Req, Default, fun cow_cookie:parse_cookie/1);
parse_header(Name = <<"expect">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2)
end);
parse_header(Name, Req, Default)
when Name =:= <<"if-match">>;
Name =:= <<"if-none-match">> ->
parse_header(Name, Req, Default, fun cowboy_http:entity_tag_match/1);
parse_header(Name, Req, Default)
when Name =:= <<"if-modified-since">>;
Name =:= <<"if-unmodified-since">> ->
parse_header(Name, Req, Default, fun cowboy_http:http_date/1);
parse_header(Name = <<"range">>, Req, Default) ->
parse_header(Name, Req, Default, fun cowboy_http:range/1);
parse_header(Name, Req, Default)
when Name =:= <<"sec-websocket-protocol">>;
Name =:= <<"x-forwarded-for">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token/2)
end);
parse_header(Name = <<"transfer-encoding">>, Req, Default) ->
parse_header(Name, Req, Default, fun cow_http_hd:parse_transfer_encoding/1);
%% @todo Product version.
parse_header(Name = <<"upgrade">>, Req, Default) ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
end);
parse_header(Name = <<"sec-websocket-extensions">>, Req, Default) ->
parse_header(Name, Req, Default, fun cowboy_http:parameterized_tokens/1);
parse_header(Name, Req, Default) ->
{Value, Req2} = header(Name, Req, Default),
{undefined, Value, Req2}.
parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) ->
case header(Name, Req) of
{undefined, Req2} ->
{ok, Default, Req2#http_req{p_headers=[{Name, Default}|PHeaders]}};
{Value, Req2} ->
case Fun(Value) of
{error, badarg} ->
{error, badarg};
P ->
{ok, P, Req2#http_req{p_headers=[{Name, P}|PHeaders]}}
end
end.
-spec cookie(binary(), Req)
-> {binary() | undefined, Req} when Req::req().
cookie(Name, Req) when is_binary(Name) ->
cookie(Name, Req, undefined).
-spec cookie(binary(), Req, Default)
-> {binary() | Default, Req} when Req::req(), Default::any().
cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) ->
case parse_header(<<"cookie">>, Req) of
{ok, undefined, Req2} ->
{Default, Req2#http_req{cookies=[]}};
{ok, Cookies, Req2} ->
cookie(Name, Req2#http_req{cookies=Cookies}, Default)
end;
cookie(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.cookies) of
{Name, Value} -> {Value, Req};
false -> {Default, Req}
end.
-spec cookies(Req) -> {list({binary(), binary()}), Req} when Req::req().
cookies(Req=#http_req{cookies=undefined}) ->
case parse_header(<<"cookie">>, Req) of
{ok, undefined, Req2} ->
{[], Req2#http_req{cookies=[]}};
{ok, Cookies, Req2} ->
cookies(Req2#http_req{cookies=Cookies});
%% Flash player incorrectly sends an empty Cookie header.
{error, badarg} ->
{[], Req#http_req{cookies=[]}}
end;
cookies(Req=#http_req{cookies=Cookies}) ->
{Cookies, Req}.
-spec meta(atom(), Req) -> {any() | undefined, Req} when Req::req().
meta(Name, Req) ->
meta(Name, Req, undefined).
-spec meta(atom(), Req, any()) -> {any(), Req} when Req::req().
meta(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.meta) of
{Name, Value} -> {Value, Req};
false -> {Default, Req}
end.
-spec set_meta(atom(), any(), Req) -> Req when Req::req().
set_meta(Name, Value, Req=#http_req{meta=Meta}) ->
Req#http_req{meta=lists:keystore(Name, 1, Meta, {Name, Value})}.
%% Request Body API.
-spec has_body(req()) -> boolean().
has_body(Req) ->
case lists:keyfind(<<"content-length">>, 1, Req#http_req.headers) of
{_, <<"0">>} ->
false;
{_, _} ->
true;
_ ->
lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers)
end.
%% The length may not be known if Transfer-Encoding is not identity,
%% and the body hasn't been read at the time of the call.
-spec body_length(Req) -> {undefined | non_neg_integer(), Req} when Req::req().
body_length(Req) ->
case parse_header(<<"transfer-encoding">>, Req) of
{ok, [<<"identity">>], Req2} ->
{ok, Length, Req3} = parse_header(<<"content-length">>, Req2, 0),
{Length, Req3};
{ok, _, Req2} ->
{undefined, Req2}
end.
-spec body(Req)
-> {ok, binary(), Req} | {more, binary(), Req}
| {error, atom()} when Req::req().
body(Req) ->
body(Req, []).
-spec body(Req, body_opts())
-> {ok, binary(), Req} | {more, binary(), Req}
| {error, atom()} when Req::req().
body(Req=#http_req{body_state=waiting}, Opts) ->
%% Send a 100 continue if needed (enabled by default).
Req1 = case lists:keyfind(continue, 1, Opts) of
{_, false} ->
Req;
_ ->
{ok, ExpectHeader, Req0} = parse_header(<<"expect">>, Req),
ok = case ExpectHeader of
[<<"100-continue">>] -> continue(Req0);
_ -> ok
end,
Req0
end,
%% Initialize body streaming state.
CFun = case lists:keyfind(content_decode, 1, Opts) of
false ->
fun cowboy_http:ce_identity/1;
{_, CFun0} ->
CFun0
end,
case lists:keyfind(transfer_decode, 1, Opts) of
false ->
case parse_header(<<"transfer-encoding">>, Req1) of
{ok, [<<"chunked">>], Req2} ->
body(Req2#http_req{body_state={stream, 0,
fun cow_http_te:stream_chunked/2, {0, 0}, CFun}}, Opts);
{ok, [<<"identity">>], Req2} ->
{Len, Req3} = body_length(Req2),
case Len of
0 ->
{ok, <<>>, Req3#http_req{body_state=done}};
_ ->
body(Req3#http_req{body_state={stream, Len,
fun cow_http_te:stream_identity/2, {0, Len},
CFun}}, Opts)
end
end;
{_, TFun, TState} ->
body(Req1#http_req{body_state={stream, 0,
TFun, TState, CFun}}, Opts)
end;
body(Req=#http_req{body_state=done}, _) ->
{ok, <<>>, Req};
body(Req, Opts) ->
ChunkLen = case lists:keyfind(length, 1, Opts) of
false -> 8000000;
{_, ChunkLen0} -> ChunkLen0
end,
ReadLen = case lists:keyfind(read_length, 1, Opts) of
false -> 1000000;
{_, ReadLen0} -> ReadLen0
end,
ReadTimeout = case lists:keyfind(read_timeout, 1, Opts) of
false -> 15000;
{_, ReadTimeout0} -> ReadTimeout0
end,
body_loop(Req, ReadTimeout, ReadLen, ChunkLen, <<>>).
body_loop(Req=#http_req{buffer=Buffer, body_state={stream, Length, _, _, _}},
ReadTimeout, ReadLength, ChunkLength, Acc) ->
{Tag, Res, Req2} = case Buffer of
<<>> ->
body_recv(Req, ReadTimeout, min(Length, ReadLength));
_ ->
body_decode(Req, ReadTimeout)
end,
case {Tag, Res} of
{ok, {ok, Data}} ->
{ok, << Acc/binary, Data/binary >>, Req2};
{more, {ok, Data}} ->
Acc2 = << Acc/binary, Data/binary >>,
case byte_size(Acc2) >= ChunkLength of
true -> {more, Acc2, Req2};
false -> body_loop(Req2, ReadTimeout, ReadLength, ChunkLength, Acc2)
end;
_ -> %% Error.
Res
end.
body_recv(Req=#http_req{transport=Transport, socket=Socket, buffer=Buffer},
ReadTimeout, ReadLength) ->
case Transport:recv(Socket, ReadLength, ReadTimeout) of
{ok, Data} ->
body_decode(Req#http_req{buffer= << Buffer/binary, Data/binary >>},
ReadTimeout);
Error = {error, _} ->
{error, Error, Req}
end.
%% Two decodings happen. First a decoding function is applied to the
%% transferred data, and then another is applied to the actual content.
%%
%% Transfer encoding is generally used for chunked bodies. The decoding
%% function uses a state to keep track of how much it has read, which is
%% also initialized through this function.
%%
%% Content encoding is generally used for compression.
%%
%% @todo Handle chunked after-the-facts headers.
%% @todo Depending on the length returned we might want to 0 or +5 it.
body_decode(Req=#http_req{buffer=Data, body_state={stream, _,
TDecode, TState, CDecode}}, ReadTimeout) ->
case TDecode(Data, TState) of
more ->
body_recv(Req#http_req{body_state={stream, 0,
TDecode, TState, CDecode}}, ReadTimeout, 0);
{more, Data2, TState2} ->
{more, CDecode(Data2), Req#http_req{body_state={stream, 0,
TDecode, TState2, CDecode}, buffer= <<>>}};
{more, Data2, Length, TState2} when is_integer(Length) ->
{more, CDecode(Data2), Req#http_req{body_state={stream, Length,
TDecode, TState2, CDecode}, buffer= <<>>}};
{more, Data2, Rest, TState2} ->
{more, CDecode(Data2), Req#http_req{body_state={stream, 0,
TDecode, TState2, CDecode}, buffer=Rest}};
{done, TotalLength, Rest} ->
{ok, {ok, <<>>}, body_decode_end(Req, TotalLength, Rest)};
{done, Data2, TotalLength, Rest} ->
{ok, CDecode(Data2), body_decode_end(Req, TotalLength, Rest)}
end.
body_decode_end(Req=#http_req{headers=Headers, p_headers=PHeaders},
TotalLength, Rest) ->
Headers2 = lists:keystore(<<"content-length">>, 1, Headers,
{<<"content-length">>, integer_to_binary(TotalLength)}),
%% At this point we just assume TEs were all decoded.
Headers3 = lists:keydelete(<<"transfer-encoding">>, 1, Headers2),
PHeaders2 = lists:keystore(<<"content-length">>, 1, PHeaders,
{<<"content-length">>, TotalLength}),
PHeaders3 = lists:keydelete(<<"transfer-encoding">>, 1, PHeaders2),
Req#http_req{buffer=Rest, body_state=done,
headers=Headers3, p_headers=PHeaders3}.
-spec body_qs(Req)
-> {ok, [{binary(), binary() | true}], Req} | {error, atom()}
when Req::req().
body_qs(Req) ->
body_qs(Req, [
{length, 64000},
{read_length, 64000},
{read_timeout, 5000}]).
-spec body_qs(Req, body_opts()) -> {ok, [{binary(), binary() | true}], Req}
| {badlength, Req} | {error, atom()} when Req::req().
body_qs(Req, Opts) ->
case body(Req, Opts) of
{ok, Body, Req2} ->
{ok, cow_qs:parse_qs(Body), Req2};
{more, _, Req2} ->
{badlength, Req2};
{error, Reason} ->
{error, Reason}
end.
%% Multipart API.
-spec part(Req)
-> {ok, cow_multipart:headers(), Req} | {done, Req}
when Req::req().
part(Req) ->
part(Req, [
{length, 64000},
{read_length, 64000},
{read_timeout, 5000}]).
-spec part(Req, body_opts())
-> {ok, cow_multipart:headers(), Req} | {done, Req}
when Req::req().
part(Req=#http_req{multipart=undefined}, Opts) ->
part(init_multipart(Req), Opts);
part(Req, Opts) ->
{Data, Req2} = stream_multipart(Req, Opts),
part(Data, Opts, Req2).
part(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}) ->
case cow_multipart:parse_headers(Buffer, Boundary) of
more ->
{Data, Req2} = stream_multipart(Req, Opts),
part(<< Buffer/binary, Data/binary >>, Opts, Req2);
{more, Buffer2} ->
{Data, Req2} = stream_multipart(Req, Opts),
part(<< Buffer2/binary, Data/binary >>, Opts, Req2);
{ok, Headers, Rest} ->
{ok, Headers, Req#http_req{multipart={Boundary, Rest}}};
%% Ignore epilogue.
{done, _} ->
{done, Req#http_req{multipart=undefined}}
end.
-spec part_body(Req)
-> {ok, binary(), Req} | {more, binary(), Req}
when Req::req().
part_body(Req) ->
part_body(Req, []).
-spec part_body(Req, body_opts())
-> {ok, binary(), Req} | {more, binary(), Req}
when Req::req().
part_body(Req=#http_req{multipart=undefined}, Opts) ->
part_body(init_multipart(Req), Opts);
part_body(Req, Opts) ->
part_body(<<>>, Opts, Req, <<>>).
part_body(Buffer, Opts, Req=#http_req{multipart={Boundary, _}}, Acc) ->
ChunkLen = case lists:keyfind(length, 1, Opts) of
false -> 8000000;
{_, ChunkLen0} -> ChunkLen0
end,
case byte_size(Acc) > ChunkLen of
true ->
{more, Acc, Req#http_req{multipart={Boundary, Buffer}}};
false ->
{Data, Req2} = stream_multipart(Req, Opts),
case cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of
{ok, Body} ->
part_body(<<>>, Opts, Req2, << Acc/binary, Body/binary >>);
{ok, Body, Rest} ->
part_body(Rest, Opts, Req2, << Acc/binary, Body/binary >>);
done ->
{ok, Acc, Req2};
{done, Body} ->
{ok, << Acc/binary, Body/binary >>, Req2};
{done, Body, Rest} ->
{ok, << Acc/binary, Body/binary >>,
Req2#http_req{multipart={Boundary, Rest}}}
end
end.
init_multipart(Req) ->
{ok, {<<"multipart">>, _, Params}, Req2}
= parse_header(<<"content-type">>, Req),
{_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params),
Req2#http_req{multipart={Boundary, <<>>}}.
stream_multipart(Req=#http_req{body_state=BodyState, multipart={_, <<>>}}, Opts) ->
true = BodyState =/= done,
{_, Data, Req2} = body(Req, Opts),
{Data, Req2};
stream_multipart(Req=#http_req{multipart={Boundary, Buffer}}, _) ->
{Buffer, Req#http_req{multipart={Boundary, <<>>}}}.
%% Response API.
%% The cookie name cannot contain any of the following characters:
%% =,;\s\t\r\n\013\014
%%
%% The cookie value cannot contain any of the following characters:
%% ,; \t\r\n\013\014
-spec set_resp_cookie(iodata(), iodata(), cookie_opts(), Req)
-> Req when Req::req().
set_resp_cookie(Name, Value, Opts, Req) ->
Cookie = cow_cookie:setcookie(Name, Value, Opts),
set_resp_header(<<"set-cookie">>, Cookie, Req).
-spec set_resp_header(binary(), iodata(), Req)
-> Req when Req::req().
set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) ->
Req#http_req{resp_headers=[{Name, Value}|RespHeaders]}.
-spec set_resp_body(iodata(), Req) -> Req when Req::req().
set_resp_body(Body, Req) ->
Req#http_req{resp_body=Body}.
-spec set_resp_body_fun(resp_body_fun(), Req) -> Req when Req::req().
set_resp_body_fun(StreamFun, Req) when is_function(StreamFun) ->
Req#http_req{resp_body=StreamFun}.
%% If the body function crashes while writing the response body or writes
%% fewer bytes than declared the behaviour is undefined.
-spec set_resp_body_fun(non_neg_integer(), resp_body_fun(), Req)
-> Req when Req::req();
(chunked, resp_chunked_fun(), Req)
-> Req when Req::req().
set_resp_body_fun(StreamLen, StreamFun, Req)
when is_integer(StreamLen), is_function(StreamFun) ->
Req#http_req{resp_body={StreamLen, StreamFun}};
set_resp_body_fun(chunked, StreamFun, Req)
when is_function(StreamFun) ->
Req#http_req{resp_body={chunked, StreamFun}}.
-spec has_resp_header(binary(), req()) -> boolean().
has_resp_header(Name, #http_req{resp_headers=RespHeaders}) ->
lists:keymember(Name, 1, RespHeaders).
-spec has_resp_body(req()) -> boolean().
has_resp_body(#http_req{resp_body=RespBody}) when is_function(RespBody) ->
true;
has_resp_body(#http_req{resp_body={chunked, _}}) ->
true;
has_resp_body(#http_req{resp_body={Length, _}}) ->
Length > 0;
has_resp_body(#http_req{resp_body=RespBody}) ->
iolist_size(RespBody) > 0.
-spec delete_resp_header(binary(), Req)
-> Req when Req::req().
delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
RespHeaders2 = lists:keydelete(Name, 1, RespHeaders),
Req#http_req{resp_headers=RespHeaders2}.
-spec reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
reply(Status, Req=#http_req{resp_body=Body}) ->
reply(Status, [], Body, Req).
-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
reply(Status, Headers, Body, Req).
-spec reply(cowboy:http_status(), cowboy:http_headers(),
iodata() | {non_neg_integer() | resp_body_fun()}, Req)
-> {ok, Req} when Req::req().
reply(Status, Headers, Body, Req=#http_req{
socket=Socket, transport=Transport,
version=Version, connection=Connection,
method=Method, resp_compress=Compress,
resp_state=RespState, resp_headers=RespHeaders})
when RespState =:= waiting; RespState =:= waiting_stream ->
HTTP11Headers = if
Transport =/= cowboy_spdy, Version =:= 'HTTP/1.0', Connection =:= keepalive ->
[{<<"connection">>, atom_to_connection(Connection)}];
Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1', Connection =:= close ->
[{<<"connection">>, atom_to_connection(Connection)}];
true ->
[]
end,
Req3 = case Body of
BodyFun when is_function(BodyFun) ->
%% We stream the response body until we close the connection.
RespConn = close,
{RespType, Req2} = if
Transport =:= cowboy_spdy ->
response(Status, Headers, RespHeaders, [
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
], stream, Req);
true ->
response(Status, Headers, RespHeaders, [
{<<"connection">>, <<"close">>},
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>},
{<<"transfer-encoding">>, <<"identity">>}
], <<>>, Req)
end,
if RespType =/= hook, Method =/= <<"HEAD">> ->
BodyFun(Socket, Transport);
true -> ok
end,
Req2#http_req{connection=RespConn};
{chunked, BodyFun} ->
%% We stream the response body in chunks.
{RespType, Req2} = chunked_response(Status, Headers, Req),
if RespType =/= hook, Method =/= <<"HEAD">> ->
ChunkFun = fun(IoData) -> chunk(IoData, Req2) end,
BodyFun(ChunkFun),
%% Send the last chunk if chunked encoding was used.
if
Version =:= 'HTTP/1.0'; RespState =:= waiting_stream ->
Req2;
true ->
last_chunk(Req2)
end;
true -> Req2
end;
{ContentLength, BodyFun} ->
%% We stream the response body for ContentLength bytes.
RespConn = response_connection(Headers, Connection),
{RespType, Req2} = response(Status, Headers, RespHeaders, [
{<<"content-length">>, integer_to_list(ContentLength)},
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
|HTTP11Headers], stream, Req),
if RespType =/= hook, Method =/= <<"HEAD">> ->
BodyFun(Socket, Transport);
true -> ok
end,
Req2#http_req{connection=RespConn};
_ when Compress ->
RespConn = response_connection(Headers, Connection),
Req2 = reply_may_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method),
Req2#http_req{connection=RespConn};
_ ->
RespConn = response_connection(Headers, Connection),
Req2 = reply_no_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method, iolist_size(Body)),
Req2#http_req{connection=RespConn}
end,
{ok, Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
reply_may_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method) ->
BodySize = iolist_size(Body),
case parse_header(<<"accept-encoding">>, Req) of
{ok, Encodings, Req2} ->
CanGzip = (BodySize > 300)
andalso (false =:= lists:keyfind(<<"content-encoding">>,
1, Headers))
andalso (false =:= lists:keyfind(<<"content-encoding">>,
1, RespHeaders))
andalso (false =:= lists:keyfind(<<"transfer-encoding">>,
1, Headers))
andalso (false =:= lists:keyfind(<<"transfer-encoding">>,
1, RespHeaders))
andalso (Encodings =/= undefined)
andalso (false =/= lists:keyfind(<<"gzip">>, 1, Encodings)),
case CanGzip of
true ->
GzBody = zlib:gzip(Body),
{_, Req3} = response(Status, Headers, RespHeaders, [
{<<"content-length">>, integer_to_list(byte_size(GzBody))},
{<<"content-encoding">>, <<"gzip">>},
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
|HTTP11Headers],
case Method of <<"HEAD">> -> <<>>; _ -> GzBody end,
Req2),
Req3;
false ->
reply_no_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method, BodySize)
end;
{error, badarg} ->
reply_no_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method, BodySize)
end.
reply_no_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method, BodySize) ->
{_, Req2} = response(Status, Headers, RespHeaders, [
{<<"content-length">>, integer_to_list(BodySize)},
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
|HTTP11Headers],
case Method of <<"HEAD">> -> <<>>; _ -> Body end,
Req),
Req2.
-spec chunked_reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
chunked_reply(Status, Req) ->
chunked_reply(Status, [], Req).
-spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
chunked_reply(Status, Headers, Req) ->
{_, Req2} = chunked_response(Status, Headers, Req),
{ok, Req2}.
-spec chunk(iodata(), req()) -> ok | {error, atom()}.
chunk(_Data, #http_req{method= <<"HEAD">>}) ->
ok;
chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy,
resp_state=chunks}) ->
cowboy_spdy:stream_data(Socket, Data);
chunk(Data, #http_req{socket=Socket, transport=Transport,
resp_state=stream}) ->
Transport:send(Socket, Data);
chunk(Data, #http_req{socket=Socket, transport=Transport,
resp_state=chunks}) ->
Transport:send(Socket, [integer_to_list(iolist_size(Data), 16),
<<"\r\n">>, Data, <<"\r\n">>]).
%% If ever made public, need to send nothing if HEAD.
-spec last_chunk(Req) -> Req when Req::req().
last_chunk(Req=#http_req{socket=Socket, transport=cowboy_spdy}) ->
_ = cowboy_spdy:stream_close(Socket),
Req#http_req{resp_state=done};
last_chunk(Req=#http_req{socket=Socket, transport=Transport}) ->
_ = Transport:send(Socket, <<"0\r\n\r\n">>),
Req#http_req{resp_state=done}.
-spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
upgrade_reply(Status, Headers, Req=#http_req{transport=Transport,
resp_state=waiting, resp_headers=RespHeaders})
when Transport =/= cowboy_spdy ->
{_, Req2} = response(Status, Headers, RespHeaders, [
{<<"connection">>, <<"Upgrade">>}
], <<>>, Req),
{ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
-spec continue(req()) -> ok | {error, atom()}.
continue(#http_req{socket=Socket, transport=Transport,
version=Version}) ->
HTTPVer = atom_to_binary(Version, latin1),
Transport:send(Socket,
<< HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>).
%% Meant to be used internally for sending errors after crashes.
-spec maybe_reply([{module(), atom(), arity() | [term()], _}], req()) -> ok.
maybe_reply(Stacktrace, Req) ->
receive
{cowboy_req, resp_sent} -> ok
after 0 ->
_ = do_maybe_reply(Stacktrace, Req),
ok
end.
do_maybe_reply([
{cow_http_hd, _, _, _},
{cowboy_req, parse_header, _, _}|_], Req) ->
cowboy_req:reply(400, Req);
do_maybe_reply(_, Req) ->
cowboy_req:reply(500, Req).
-spec ensure_response(req(), cowboy:http_status()) -> ok.
%% The response has already been fully sent to the client.
ensure_response(#http_req{resp_state=done}, _) ->
ok;
%% No response has been sent but everything apparently went fine.
%% Reply with the status code found in the second argument.
ensure_response(Req=#http_req{resp_state=RespState}, Status)
when RespState =:= waiting; RespState =:= waiting_stream ->
_ = reply(Status, [], [], Req),
ok;
%% Terminate the chunked body for HTTP/1.1 only.
ensure_response(#http_req{method= <<"HEAD">>}, _) ->
ok;
ensure_response(Req=#http_req{resp_state=chunks}, _) ->
_ = last_chunk(Req),
ok;
ensure_response(#http_req{}, _) ->
ok.
%% Private setter/getter API.
-spec append_buffer(binary(), Req) -> Req when Req::req().
append_buffer(Suffix, Req=#http_req{buffer=Buffer}) ->
Req#http_req{buffer= << Buffer/binary, Suffix/binary >>}.
-spec get(atom(), req()) -> any(); ([atom()], req()) -> any().
get(List, Req) when is_list(List) ->
[g(Atom, Req) || Atom <- List];
get(Atom, Req) when is_atom(Atom) ->
g(Atom, Req).
g(bindings, #http_req{bindings=Ret}) -> Ret;
g(body_state, #http_req{body_state=Ret}) -> Ret;
g(buffer, #http_req{buffer=Ret}) -> Ret;
g(connection, #http_req{connection=Ret}) -> Ret;
g(cookies, #http_req{cookies=Ret}) -> Ret;
g(headers, #http_req{headers=Ret}) -> Ret;
g(host, #http_req{host=Ret}) -> Ret;
g(host_info, #http_req{host_info=Ret}) -> Ret;
g(meta, #http_req{meta=Ret}) -> Ret;
g(method, #http_req{method=Ret}) -> Ret;
g(multipart, #http_req{multipart=Ret}) -> Ret;
g(onresponse, #http_req{onresponse=Ret}) -> Ret;
g(p_headers, #http_req{p_headers=Ret}) -> Ret;
g(path, #http_req{path=Ret}) -> Ret;
g(path_info, #http_req{path_info=Ret}) -> Ret;
g(peer, #http_req{peer=Ret}) -> Ret;
g(pid, #http_req{pid=Ret}) -> Ret;
g(port, #http_req{port=Ret}) -> Ret;
g(qs, #http_req{qs=Ret}) -> Ret;
g(qs_vals, #http_req{qs_vals=Ret}) -> Ret;
g(resp_body, #http_req{resp_body=Ret}) -> Ret;
g(resp_compress, #http_req{resp_compress=Ret}) -> Ret;
g(resp_headers, #http_req{resp_headers=Ret}) -> Ret;
g(resp_state, #http_req{resp_state=Ret}) -> Ret;
g(socket, #http_req{socket=Ret}) -> Ret;
g(transport, #http_req{transport=Ret}) -> Ret;
g(version, #http_req{version=Ret}) -> Ret.
-spec set([{atom(), any()}], Req) -> Req when Req::req().
set([], Req) -> Req;
set([{bindings, Val}|Tail], Req) -> set(Tail, Req#http_req{bindings=Val});
set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val});
set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val});
set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val});
set([{cookies, Val}|Tail], Req) -> set(Tail, Req#http_req{cookies=Val});
set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val});
set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val});
set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val});
set([{meta, Val}|Tail], Req) -> set(Tail, Req#http_req{meta=Val});
set([{method, Val}|Tail], Req) -> set(Tail, Req#http_req{method=Val});
set([{multipart, Val}|Tail], Req) -> set(Tail, Req#http_req{multipart=Val});
set([{onresponse, Val}|Tail], Req) -> set(Tail, Req#http_req{onresponse=Val});
set([{p_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{p_headers=Val});
set([{path, Val}|Tail], Req) -> set(Tail, Req#http_req{path=Val});
set([{path_info, Val}|Tail], Req) -> set(Tail, Req#http_req{path_info=Val});
set([{peer, Val}|Tail], Req) -> set(Tail, Req#http_req{peer=Val});
set([{pid, Val}|Tail], Req) -> set(Tail, Req#http_req{pid=Val});
set([{port, Val}|Tail], Req) -> set(Tail, Req#http_req{port=Val});
set([{qs, Val}|Tail], Req) -> set(Tail, Req#http_req{qs=Val});
set([{qs_vals, Val}|Tail], Req) -> set(Tail, Req#http_req{qs_vals=Val});
set([{resp_body, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_body=Val});
set([{resp_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_headers=Val});
set([{resp_state, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_state=Val});
set([{socket, Val}|Tail], Req) -> set(Tail, Req#http_req{socket=Val});
set([{transport, Val}|Tail], Req) -> set(Tail, Req#http_req{transport=Val});
set([{version, Val}|Tail], Req) -> set(Tail, Req#http_req{version=Val}).
-spec set_bindings(cowboy_router:tokens(), cowboy_router:tokens(),
cowboy_router:bindings(), Req) -> Req when Req::req().
set_bindings(HostInfo, PathInfo, Bindings, Req) ->
Req#http_req{host_info=HostInfo, path_info=PathInfo,
bindings=Bindings}.
%% Misc API.
-spec compact(Req) -> Req when Req::req().
compact(Req) ->
Req#http_req{host_info=undefined,
path_info=undefined, qs_vals=undefined,
bindings=undefined, headers=[],
p_headers=[], cookies=[]}.
-spec lock(Req) -> Req when Req::req().
lock(Req) ->
Req#http_req{resp_state=locked}.
-spec to_list(req()) -> [{atom(), any()}].
to_list(Req) ->
lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))).
%% Internal.
-spec chunked_response(cowboy:http_status(), cowboy:http_headers(), Req) ->
{normal | hook, Req} when Req::req().
chunked_response(Status, Headers, Req=#http_req{
transport=cowboy_spdy, resp_state=waiting,
resp_headers=RespHeaders}) ->
{RespType, Req2} = response(Status, Headers, RespHeaders, [
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
], stream, Req),
{RespType, Req2#http_req{resp_state=chunks,
resp_headers=[], resp_body= <<>>}};
chunked_response(Status, Headers, Req=#http_req{
version=Version, connection=Connection,
resp_state=RespState, resp_headers=RespHeaders})
when RespState =:= waiting; RespState =:= waiting_stream ->
RespConn = response_connection(Headers, Connection),
HTTP11Headers = if
Version =:= 'HTTP/1.0', Connection =:= keepalive ->
[{<<"connection">>, atom_to_connection(Connection)}];
Version =:= 'HTTP/1.0' -> [];
true ->
MaybeTE = if
RespState =:= waiting_stream -> [];
true -> [{<<"transfer-encoding">>, <<"chunked">>}]
end,
if
Connection =:= close ->
[{<<"connection">>, atom_to_connection(Connection)}|MaybeTE];
true ->
MaybeTE
end
end,
RespState2 = if
Version =:= 'HTTP/1.1', RespState =:= 'waiting' -> chunks;
true -> stream
end,
{RespType, Req2} = response(Status, Headers, RespHeaders, [
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
|HTTP11Headers], <<>>, Req),
{RespType, Req2#http_req{connection=RespConn, resp_state=RespState2,
resp_headers=[], resp_body= <<>>}}.
-spec response(cowboy:http_status(), cowboy:http_headers(),
cowboy:http_headers(), cowboy:http_headers(), stream | iodata(), Req)
-> {normal | hook, Req} when Req::req().
response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
socket=Socket, transport=Transport, version=Version,
pid=ReqPid, onresponse=OnResponse}) ->
FullHeaders = case OnResponse of
already_called -> Headers;
_ -> response_merge_headers(Headers, RespHeaders, DefaultHeaders)
end,
Body2 = case Body of stream -> <<>>; _ -> Body end,
{Status2, FullHeaders2, Req2} = case OnResponse of
already_called -> {Status, FullHeaders, Req};
undefined -> {Status, FullHeaders, Req};
OnResponse ->
case OnResponse(Status, FullHeaders, Body2,
%% Don't call 'onresponse' from the hook itself.
Req#http_req{resp_headers=[], resp_body= <<>>,
onresponse=already_called}) of
StHdReq = {_, _, _} ->
StHdReq;
Req1 ->
{Status, FullHeaders, Req1}
end
end,
ReplyType = case Req2#http_req.resp_state of
waiting when Transport =:= cowboy_spdy, Body =:= stream ->
cowboy_spdy:stream_reply(Socket, status(Status2), FullHeaders2),
ReqPid ! {?MODULE, resp_sent},
normal;
waiting when Transport =:= cowboy_spdy ->
cowboy_spdy:reply(Socket, status(Status2), FullHeaders2, Body),
ReqPid ! {?MODULE, resp_sent},
normal;
RespState when RespState =:= waiting; RespState =:= waiting_stream ->
HTTPVer = atom_to_binary(Version, latin1),
StatusLine = << HTTPVer/binary, " ",
(status(Status2))/binary, "\r\n" >>,
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
|| {Key, Value} <- FullHeaders2],
Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]),
ReqPid ! {?MODULE, resp_sent},
normal;
_ ->
hook
end,
{ReplyType, Req2}.
-spec response_connection(cowboy:http_headers(), keepalive | close)
-> keepalive | close.
response_connection([], Connection) ->
Connection;
response_connection([{Name, Value}|Tail], Connection) ->
case Name of
<<"connection">> ->
Tokens = cow_http_hd:parse_connection(Value),
connection_to_atom(Tokens);
_ ->
response_connection(Tail, Connection)
end.
-spec response_merge_headers(cowboy:http_headers(), cowboy:http_headers(),
cowboy:http_headers()) -> cowboy:http_headers().
response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
Headers2 = [{Key, Value} || {Key, Value} <- Headers],
merge_headers(
merge_headers(Headers2, RespHeaders),
DefaultHeaders).
-spec merge_headers(cowboy:http_headers(), cowboy:http_headers())
-> cowboy:http_headers().
%% Merge headers by prepending the tuples in the second list to the
%% first list. It also handles Set-Cookie properly, which supports
%% duplicated entries. Notice that, while the RFC2109 does allow more
%% than one cookie to be set per Set-Cookie header, we are following
%% the implementation of common web servers and applications which
%% return many distinct headers per each Set-Cookie entry to avoid
%% issues with clients/browser which may not support it.
merge_headers(Headers, []) ->
Headers;
merge_headers(Headers, [{<<"set-cookie">>, Value}|Tail]) ->
merge_headers([{<<"set-cookie">>, Value}|Headers], Tail);
merge_headers(Headers, [{Name, Value}|Tail]) ->
Headers2 = case lists:keymember(Name, 1, Headers) of
true -> Headers;
false -> [{Name, Value}|Headers]
end,
merge_headers(Headers2, Tail).
-spec atom_to_connection(keepalive) -> <<_:80>>;
(close) -> <<_:40>>.
atom_to_connection(keepalive) ->
<<"keep-alive">>;
atom_to_connection(close) ->
<<"close">>.
%% We don't match on "keep-alive" since it is the default value.
-spec connection_to_atom([binary()]) -> keepalive | close.
connection_to_atom([]) ->
keepalive;
connection_to_atom([<<"close">>|_]) ->
close;
connection_to_atom([_|Tail]) ->
connection_to_atom(Tail).
-spec status(cowboy:http_status()) -> binary().
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
status(102) -> <<"102 Processing">>;
status(200) -> <<"200 OK">>;
status(201) -> <<"201 Created">>;
status(202) -> <<"202 Accepted">>;
status(203) -> <<"203 Non-Authoritative Information">>;
status(204) -> <<"204 No Content">>;
status(205) -> <<"205 Reset Content">>;
status(206) -> <<"206 Partial Content">>;
status(207) -> <<"207 Multi-Status">>;
status(226) -> <<"226 IM Used">>;
status(300) -> <<"300 Multiple Choices">>;
status(301) -> <<"301 Moved Permanently">>;
status(302) -> <<"302 Found">>;
status(303) -> <<"303 See Other">>;
status(304) -> <<"304 Not Modified">>;
status(305) -> <<"305 Use Proxy">>;
status(306) -> <<"306 Switch Proxy">>;
status(307) -> <<"307 Temporary Redirect">>;
status(400) -> <<"400 Bad Request">>;
status(401) -> <<"401 Unauthorized">>;
status(402) -> <<"402 Payment Required">>;
status(403) -> <<"403 Forbidden">>;
status(404) -> <<"404 Not Found">>;
status(405) -> <<"405 Method Not Allowed">>;
status(406) -> <<"406 Not Acceptable">>;
status(407) -> <<"407 Proxy Authentication Required">>;
status(408) -> <<"408 Request Timeout">>;
status(409) -> <<"409 Conflict">>;
status(410) -> <<"410 Gone">>;
status(411) -> <<"411 Length Required">>;
status(412) -> <<"412 Precondition Failed">>;
status(413) -> <<"413 Request Entity Too Large">>;
status(414) -> <<"414 Request-URI Too Long">>;
status(415) -> <<"415 Unsupported Media Type">>;
status(416) -> <<"416 Requested Range Not Satisfiable">>;
status(417) -> <<"417 Expectation Failed">>;
status(418) -> <<"418 I'm a teapot">>;
status(422) -> <<"422 Unprocessable Entity">>;
status(423) -> <<"423 Locked">>;
status(424) -> <<"424 Failed Dependency">>;
status(425) -> <<"425 Unordered Collection">>;
status(426) -> <<"426 Upgrade Required">>;
status(428) -> <<"428 Precondition Required">>;
status(429) -> <<"429 Too Many Requests">>;
status(431) -> <<"431 Request Header Fields Too Large">>;
status(500) -> <<"500 Internal Server Error">>;
status(501) -> <<"501 Not Implemented">>;
status(502) -> <<"502 Bad Gateway">>;
status(503) -> <<"503 Service Unavailable">>;
status(504) -> <<"504 Gateway Timeout">>;
status(505) -> <<"505 HTTP Version Not Supported">>;
status(506) -> <<"506 Variant Also Negotiates">>;
status(507) -> <<"507 Insufficient Storage">>;
status(510) -> <<"510 Not Extended">>;
status(511) -> <<"511 Network Authentication Required">>;
status(B) when is_binary(B) -> B.
%% Tests.
-ifdef(TEST).
url_test() ->
{undefined, _} =
url(#http_req{transport=ranch_tcp, host= <<>>, port= undefined,
path= <<>>, qs= <<>>, pid=self()}),
{<<"http://localhost/path">>, _ } =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80,
path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:443/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443,
path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:8080/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:8080/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
{<<"https://localhost/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443,
path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"https://localhost:8443/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"https://localhost:8443/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
ok.
connection_to_atom_test_() ->
Tests = [
{[<<"close">>], close},
{[<<"keep-alive">>], keepalive},
{[<<"keep-alive">>, <<"upgrade">>], keepalive}
],
[{lists:flatten(io_lib:format("~p", [T])),
fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
merge_headers_test_() ->
Tests = [
{[{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
[{<<"set-cookie">>,<<"foo=bar">>},{<<"content-length">>,<<"11">>}],
[{<<"set-cookie">>,<<"foo=bar">>},
{<<"content-length">>,<<"13">>},
{<<"server">>,<<"Cowboy">>}]},
{[{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
[{<<"set-cookie">>,<<"foo=bar">>},{<<"set-cookie">>,<<"bar=baz">>}],
[{<<"set-cookie">>,<<"bar=baz">>},
{<<"set-cookie">>,<<"foo=bar">>},
{<<"content-length">>,<<"13">>},
{<<"server">>,<<"Cowboy">>}]}
],
[fun() -> Res = merge_headers(L,R) end || {L, R, Res} <- Tests].
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% Originally based on the Webmachine Diagram from Alan Dean and
%% Justin Sheehy.
-module(cowboy_rest).
-behaviour(cowboy_sub_protocol).
-export([upgrade/4]).
-record(state, {
env :: cowboy_middleware:env(),
method = undefined :: binary(),
%% Handler.
handler :: atom(),
handler_state :: any(),
%% Allowed methods. Only used for OPTIONS requests.
allowed_methods :: [binary()],
%% Media type.
content_types_p = [] ::
[{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
atom()}],
content_type_a :: undefined
| {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
atom()},
%% Language.
languages_p = [] :: [binary()],
language_a :: undefined | binary(),
%% Charset.
charsets_p = [] :: [binary()],
charset_a :: undefined | binary(),
%% Whether the resource exists.
exists = false :: boolean(),
%% Cached resource calls.
etag :: undefined | no_call | {strong | weak, binary()},
last_modified :: undefined | no_call | calendar:datetime(),
expires :: undefined | no_call | calendar:datetime() | binary()
}).
-spec upgrade(Req, Env, module(), any())
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req, Env, Handler, HandlerOpts) ->
Method = cowboy_req:get(method, Req),
case erlang:function_exported(Handler, rest_init, 2) of
true ->
try Handler:rest_init(Req, HandlerOpts) of
{ok, Req2, HandlerState} ->
service_available(Req2, #state{env=Env, method=Method,
handler=Handler, handler_state=HandlerState})
catch Class:Reason ->
Stacktrace = erlang:get_stacktrace(),
cowboy_req:maybe_reply(Stacktrace, Req),
erlang:Class([
{reason, Reason},
{mfa, {Handler, rest_init, 2}},
{stacktrace, Stacktrace},
{req, cowboy_req:to_list(Req)},
{opts, HandlerOpts}
])
end;
false ->
service_available(Req, #state{env=Env, method=Method,
handler=Handler})
end.
service_available(Req, State) ->
expect(Req, State, service_available, true, fun known_methods/2, 503).
%% known_methods/2 should return a list of binary methods.
known_methods(Req, State=#state{method=Method}) ->
case call(Req, State, known_methods) of
no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
Method =:= <<"POST">>; Method =:= <<"PUT">>;
Method =:= <<"PATCH">>; Method =:= <<"DELETE">>;
Method =:= <<"OPTIONS">> ->
next(Req, State, fun uri_too_long/2);
no_call ->
next(Req, State, 501);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{List, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState},
case lists:member(Method, List) of
true -> next(Req2, State2, fun uri_too_long/2);
false -> next(Req2, State2, 501)
end
end.
uri_too_long(Req, State) ->
expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
%% allowed_methods/2 should return a list of binary methods.
allowed_methods(Req, State=#state{method=Method}) ->
case call(Req, State, allowed_methods) of
no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
next(Req, State, fun malformed_request/2);
no_call when Method =:= <<"OPTIONS">> ->
next(Req, State#state{allowed_methods=
[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]},
fun malformed_request/2);
no_call ->
method_not_allowed(Req, State,
[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{List, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState},
case lists:member(Method, List) of
true when Method =:= <<"OPTIONS">> ->
next(Req2, State2#state{allowed_methods=List},
fun malformed_request/2);
true ->
next(Req2, State2, fun malformed_request/2);
false ->
method_not_allowed(Req2, State2, List)
end
end.
method_not_allowed(Req, State, []) ->
Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
respond(Req2, State, 405);
method_not_allowed(Req, State, Methods) ->
<< ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>,
Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
respond(Req2, State, 405).
malformed_request(Req, State) ->
expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
%% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
is_authorized(Req, State) ->
case call(Req, State, is_authorized) of
no_call ->
forbidden(Req, State);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{true, Req2, HandlerState} ->
forbidden(Req2, State#state{handler_state=HandlerState});
{{false, AuthHead}, Req2, HandlerState} ->
Req3 = cowboy_req:set_resp_header(
<<"www-authenticate">>, AuthHead, Req2),
respond(Req3, State#state{handler_state=HandlerState}, 401)
end.
forbidden(Req, State) ->
expect(Req, State, forbidden, false, fun valid_content_headers/2, 403).
valid_content_headers(Req, State) ->
expect(Req, State, valid_content_headers, true,
fun known_content_type/2, 501).
known_content_type(Req, State) ->
expect(Req, State, known_content_type, true,
fun valid_entity_length/2, 415).
valid_entity_length(Req, State) ->
expect(Req, State, valid_entity_length, true, fun options/2, 413).
%% If you need to add additional headers to the response at this point,
%% you should do it directly in the options/2 call using set_resp_headers.
options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) ->
case call(Req, State, options) of
no_call when Methods =:= [] ->
Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
respond(Req2, State, 200);
no_call ->
<< ", ", Allow/binary >>
= << << ", ", M/binary >> || M <- Methods >>,
Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
respond(Req2, State, 200);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{ok, Req2, HandlerState} ->
respond(Req2, State#state{handler_state=HandlerState}, 200)
end;
options(Req, State) ->
content_types_provided(Req, State).
%% content_types_provided/2 should return a list of content types and their
%% associated callback function as a tuple: {{Type, SubType, Params}, Fun}.
%% Type and SubType are the media type as binary. Params is a list of
%% Key/Value tuple, with Key and Value a binary. Fun is the name of the
%% callback that will be used to return the content of the response. It is
%% given as an atom.
%%
%% An example of such return value would be:
%% {{<<"text">>, <<"html">>, []}, to_html}
%%
%% Note that it is also possible to return a binary content type that will
%% then be parsed by Cowboy. However note that while this may make your
%% resources a little more readable, this is a lot less efficient.
%%
%% An example of such return value would be:
%% {<<"text/html">>, to_html}
content_types_provided(Req, State) ->
case call(Req, State, content_types_provided) of
no_call ->
State2 = State#state{
content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
case cowboy_req:parse_header(<<"accept">>, Req) of
{error, badarg} ->
respond(Req, State2, 400);
{ok, undefined, Req2} ->
languages_provided(
cowboy_req:set_meta(media_type, {<<"text">>, <<"html">>, []}, Req2),
State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
{ok, Accept, Req2} ->
Accept2 = prioritize_accept(Accept),
choose_media_type(Req2, State2, Accept2)
end;
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{[], Req2, HandlerState} ->
not_acceptable(Req2, State#state{handler_state=HandlerState});
{CTP, Req2, HandlerState} ->
CTP2 = [normalize_content_types(P) || P <- CTP],
State2 = State#state{
handler_state=HandlerState, content_types_p=CTP2},
case cowboy_req:parse_header(<<"accept">>, Req2) of
{error, badarg} ->
respond(Req2, State2, 400);
{ok, undefined, Req3} ->
{PMT, _Fun} = HeadCTP = hd(CTP2),
languages_provided(
cowboy_req:set_meta(media_type, PMT, Req3),
State2#state{content_type_a=HeadCTP});
{ok, Accept, Req3} ->
Accept2 = prioritize_accept(Accept),
choose_media_type(Req3, State2, Accept2)
end
end.
normalize_content_types({ContentType, Callback})
when is_binary(ContentType) ->
{cowboy_http:content_type(ContentType), Callback};
normalize_content_types(Normalized) ->
Normalized.
prioritize_accept(Accept) ->
lists:sort(
fun ({MediaTypeA, Quality, _AcceptParamsA},
{MediaTypeB, Quality, _AcceptParamsB}) ->
%% Same quality, check precedence in more details.
prioritize_mediatype(MediaTypeA, MediaTypeB);
({_MediaTypeA, QualityA, _AcceptParamsA},
{_MediaTypeB, QualityB, _AcceptParamsB}) ->
%% Just compare the quality.
QualityA > QualityB
end, Accept).
%% Media ranges can be overridden by more specific media ranges or
%% specific media types. If more than one media range applies to a given
%% type, the most specific reference has precedence.
%%
%% We always choose B over A when we can't decide between the two.
prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->
case TypeB of
TypeA ->
case SubTypeB of
SubTypeA -> length(ParamsA) > length(ParamsB);
<<"*">> -> true;
_Any -> false
end;
<<"*">> -> true;
_Any -> false
end.
%% Ignoring the rare AcceptParams. Not sure what should be done about them.
choose_media_type(Req, State, []) ->
not_acceptable(Req, State);
choose_media_type(Req, State=#state{content_types_p=CTP},
[MediaType|Tail]) ->
match_media_type(Req, State, Tail, CTP, MediaType).
match_media_type(Req, State, Accept, [], _MediaType) ->
choose_media_type(Req, State, Accept);
match_media_type(Req, State, Accept, CTP,
MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
match_media_type_params(Req, State, Accept, CTP, MediaType);
match_media_type(Req, State, Accept,
CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
match_media_type_params(Req, State, Accept, CTP, MediaType);
match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
match_media_type(Req, State, Accept, Tail, MediaType).
match_media_type_params(Req, State, _Accept,
[Provided = {{TP, STP, '*'}, _Fun}|_Tail],
{{_TA, _STA, Params_A}, _QA, _APA}) ->
PMT = {TP, STP, Params_A},
languages_provided(cowboy_req:set_meta(media_type, PMT, Req),
State#state{content_type_a=Provided});
match_media_type_params(Req, State, Accept,
[Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail],
MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
case lists:sort(Params_P) =:= lists:sort(Params_A) of
true ->
languages_provided(cowboy_req:set_meta(media_type, PMT, Req),
State#state{content_type_a=Provided});
false ->
match_media_type(Req, State, Accept, Tail, MediaType)
end.
%% languages_provided should return a list of binary values indicating
%% which languages are accepted by the resource.
%%
%% @todo I suppose we should also ask the resource if it wants to
%% set a language itself or if it wants it to be automatically chosen.
languages_provided(Req, State) ->
case call(Req, State, languages_provided) of
no_call ->
charsets_provided(Req, State);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{[], Req2, HandlerState} ->
not_acceptable(Req2, State#state{handler_state=HandlerState});
{LP, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState, languages_p=LP},
{ok, AcceptLanguage, Req3} =
cowboy_req:parse_header(<<"accept-language">>, Req2),
case AcceptLanguage of
undefined ->
set_language(Req3, State2#state{language_a=hd(LP)});
AcceptLanguage ->
AcceptLanguage2 = prioritize_languages(AcceptLanguage),
choose_language(Req3, State2, AcceptLanguage2)
end
end.
%% A language-range matches a language-tag if it exactly equals the tag,
%% or if it exactly equals a prefix of the tag such that the first tag
%% character following the prefix is "-". The special range "*", if
%% present in the Accept-Language field, matches every tag not matched
%% by any other range present in the Accept-Language field.
%%
%% @todo The last sentence probably means we should always put '*'
%% at the end of the list.
prioritize_languages(AcceptLanguages) ->
lists:sort(
fun ({_TagA, QualityA}, {_TagB, QualityB}) ->
QualityA > QualityB
end, AcceptLanguages).
choose_language(Req, State, []) ->
not_acceptable(Req, State);
choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->
match_language(Req, State, Tail, LP, Language).
match_language(Req, State, Accept, [], _Language) ->
choose_language(Req, State, Accept);
match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->
set_language(Req, State#state{language_a=Provided});
match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->
set_language(Req, State#state{language_a=Provided});
match_language(Req, State, Accept, [Provided|Tail],
Language = {Tag, _Quality}) ->
Length = byte_size(Tag),
case Provided of
<< Tag:Length/binary, $-, _Any/bits >> ->
set_language(Req, State#state{language_a=Provided});
_Any ->
match_language(Req, State, Accept, Tail, Language)
end.
set_language(Req, State=#state{language_a=Language}) ->
Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
charsets_provided(cowboy_req:set_meta(language, Language, Req2), State).
%% charsets_provided should return a list of binary values indicating
%% which charsets are accepted by the resource.
charsets_provided(Req, State) ->
case call(Req, State, charsets_provided) of
no_call ->
set_content_type(Req, State);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{[], Req2, HandlerState} ->
not_acceptable(Req2, State#state{handler_state=HandlerState});
{CP, Req2, HandlerState} ->
State2 = State#state{handler_state=HandlerState, charsets_p=CP},
{ok, AcceptCharset, Req3} =
cowboy_req:parse_header(<<"accept-charset">>, Req2),
case AcceptCharset of
undefined ->
set_content_type(Req3, State2#state{charset_a=hd(CP)});
AcceptCharset ->
AcceptCharset2 = prioritize_charsets(AcceptCharset),
choose_charset(Req3, State2, AcceptCharset2)
end
end.
%% The special value "*", if present in the Accept-Charset field,
%% matches every character set (including ISO-8859-1) which is not
%% mentioned elsewhere in the Accept-Charset field. If no "*" is present
%% in an Accept-Charset field, then all character sets not explicitly
%% mentioned get a quality value of 0, except for ISO-8859-1, which gets
%% a quality value of 1 if not explicitly mentioned.
prioritize_charsets(AcceptCharsets) ->
AcceptCharsets2 = lists:sort(
fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->
QualityA > QualityB
end, AcceptCharsets),
case lists:keymember(<<"*">>, 1, AcceptCharsets2) of
true -> AcceptCharsets2;
false ->
case lists:keymember(<<"iso-8859-1">>, 1, AcceptCharsets2) of
true -> AcceptCharsets2;
false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
end
end.
choose_charset(Req, State, []) ->
not_acceptable(Req, State);
choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
match_charset(Req, State, Tail, CP, Charset).
match_charset(Req, State, Accept, [], _Charset) ->
choose_charset(Req, State, Accept);
match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->
set_content_type(Req, State#state{charset_a=Provided});
match_charset(Req, State, Accept, [_|Tail], Charset) ->
match_charset(Req, State, Accept, Tail, Charset).
set_content_type(Req, State=#state{
content_type_a={{Type, SubType, Params}, _Fun},
charset_a=Charset}) ->
ParamsBin = set_content_type_build_params(Params, []),
ContentType = [Type, <<"/">>, SubType, ParamsBin],
ContentType2 = case Charset of
undefined -> ContentType;
Charset -> [ContentType, <<"; charset=">>, Charset]
end,
Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State).
set_content_type_build_params('*', []) ->
<<>>;
set_content_type_build_params([], []) ->
<<>>;
set_content_type_build_params([], Acc) ->
lists:reverse(Acc);
set_content_type_build_params([{Attr, Value}|Tail], Acc) ->
set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]).
%% @todo Match for identity as we provide nothing else for now.
%% @todo Don't forget to set the Content-Encoding header when we reply a body
%% and the found encoding is something other than identity.
encodings_provided(Req, State) ->
variances(Req, State).
not_acceptable(Req, State) ->
respond(Req, State, 406).
%% variances/2 should return a list of headers that will be added
%% to the Vary response header. The Accept, Accept-Language,
%% Accept-Charset and Accept-Encoding headers do not need to be
%% specified.
%%
%% @todo Do Accept-Encoding too when we handle it.
%% @todo Does the order matter?
variances(Req, State=#state{content_types_p=CTP,
languages_p=LP, charsets_p=CP}) ->
Variances = case CTP of
[] -> [];
[_] -> [];
[_|_] -> [<<"accept">>]
end,
Variances2 = case LP of
[] -> Variances;
[_] -> Variances;
[_|_] -> [<<"accept-language">>|Variances]
end,
Variances3 = case CP of
[] -> Variances2;
[_] -> Variances2;
[_|_] -> [<<"accept-charset">>|Variances2]
end,
try variances(Req, State, Variances3) of
{Variances4, Req2, State2} ->
case [[<<", ">>, V] || V <- Variances4] of
[] ->
resource_exists(Req2, State2);
[[<<", ">>, H]|Variances5] ->
Req3 = cowboy_req:set_resp_header(
<<"vary">>, [H|Variances5], Req2),
resource_exists(Req3, State2)
end
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, variances)
end.
variances(Req, State, Variances) ->
case unsafe_call(Req, State, variances) of
no_call ->
{Variances, Req, State};
{HandlerVariances, Req2, HandlerState} ->
{Variances ++ HandlerVariances, Req2,
State#state{handler_state=HandlerState}}
end.
resource_exists(Req, State) ->
expect(Req, State, resource_exists, true,
fun if_match_exists/2, fun if_match_must_not_exist/2).
if_match_exists(Req, State) ->
State2 = State#state{exists=true},
case cowboy_req:parse_header(<<"if-match">>, Req) of
{ok, undefined, Req2} ->
if_unmodified_since_exists(Req2, State2);
{ok, '*', Req2} ->
if_unmodified_since_exists(Req2, State2);
{ok, ETagsList, Req2} ->
if_match(Req2, State2, ETagsList);
{error, badarg} ->
respond(Req, State2, 400)
end.
if_match(Req, State, EtagsList) ->
try generate_etag(Req, State) of
{Etag, Req2, State2} ->
case lists:member(Etag, EtagsList) of
true -> if_unmodified_since_exists(Req2, State2);
%% Etag may be `undefined' which cannot be a member.
false -> precondition_failed(Req2, State2)
end
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, generate_etag)
end.
if_match_must_not_exist(Req, State) ->
case cowboy_req:header(<<"if-match">>, Req) of
{undefined, Req2} -> is_put_to_missing_resource(Req2, State);
{_Any, Req2} -> precondition_failed(Req2, State)
end.
if_unmodified_since_exists(Req, State) ->
case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
{ok, undefined, Req2} ->
if_none_match_exists(Req2, State);
{ok, IfUnmodifiedSince, Req2} ->
if_unmodified_since(Req2, State, IfUnmodifiedSince);
{error, badarg} ->
if_none_match_exists(Req, State)
end.
%% If LastModified is the atom 'no_call', we continue.
if_unmodified_since(Req, State, IfUnmodifiedSince) ->
try last_modified(Req, State) of
{LastModified, Req2, State2} ->
case LastModified > IfUnmodifiedSince of
true -> precondition_failed(Req2, State2);
false -> if_none_match_exists(Req2, State2)
end
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, last_modified)
end.
if_none_match_exists(Req, State) ->
case cowboy_req:parse_header(<<"if-none-match">>, Req) of
{ok, undefined, Req2} ->
if_modified_since_exists(Req2, State);
{ok, '*', Req2} ->
precondition_is_head_get(Req2, State);
{ok, EtagsList, Req2} ->
if_none_match(Req2, State, EtagsList);
{error, badarg} ->
respond(Req, State, 400)
end.
if_none_match(Req, State, EtagsList) ->
try generate_etag(Req, State) of
{Etag, Req2, State2} ->
case Etag of
undefined ->
precondition_failed(Req2, State2);
Etag ->
case lists:member(Etag, EtagsList) of
true -> precondition_is_head_get(Req2, State2);
false -> if_modified_since_exists(Req2, State2)
end
end
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, generate_etag)
end.
precondition_is_head_get(Req, State=#state{method=Method})
when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
not_modified(Req, State);
precondition_is_head_get(Req, State) ->
precondition_failed(Req, State).
if_modified_since_exists(Req, State) ->
case cowboy_req:parse_header(<<"if-modified-since">>, Req) of
{ok, undefined, Req2} ->
method(Req2, State);
{ok, IfModifiedSince, Req2} ->
if_modified_since_now(Req2, State, IfModifiedSince);
{error, badarg} ->
method(Req, State)
end.
if_modified_since_now(Req, State, IfModifiedSince) ->
case IfModifiedSince > erlang:universaltime() of
true -> method(Req, State);
false -> if_modified_since(Req, State, IfModifiedSince)
end.
if_modified_since(Req, State, IfModifiedSince) ->
try last_modified(Req, State) of
{no_call, Req2, State2} ->
method(Req2, State2);
{LastModified, Req2, State2} ->
case LastModified > IfModifiedSince of
true -> method(Req2, State2);
false -> not_modified(Req2, State2)
end
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, last_modified)
end.
not_modified(Req, State) ->
Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
try set_resp_etag(Req2, State) of
{Req3, State2} ->
try set_resp_expires(Req3, State2) of
{Req4, State3} ->
respond(Req4, State3, 304)
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, expires)
end
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, generate_etag)
end.
precondition_failed(Req, State) ->
respond(Req, State, 412).
is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
moved_permanently(Req, State, fun is_conflict/2);
is_put_to_missing_resource(Req, State) ->
previously_existed(Req, State).
%% moved_permanently/2 should return either false or {true, Location}
%% with Location the full new URI of the resource.
moved_permanently(Req, State, OnFalse) ->
case call(Req, State, moved_permanently) of
{{true, Location}, Req2, HandlerState} ->
Req3 = cowboy_req:set_resp_header(
<<"location">>, Location, Req2),
respond(Req3, State#state{handler_state=HandlerState}, 301);
{false, Req2, HandlerState} ->
OnFalse(Req2, State#state{handler_state=HandlerState});
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
no_call ->
OnFalse(Req, State)
end.
previously_existed(Req, State) ->
expect(Req, State, previously_existed, false,
fun (R, S) -> is_post_to_missing_resource(R, S, 404) end,
fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).
%% moved_temporarily/2 should return either false or {true, Location}
%% with Location the full new URI of the resource.
moved_temporarily(Req, State) ->
case call(Req, State, moved_temporarily) of
{{true, Location}, Req2, HandlerState} ->
Req3 = cowboy_req:set_resp_header(
<<"location">>, Location, Req2),
respond(Req3, State#state{handler_state=HandlerState}, 307);
{false, Req2, HandlerState} ->
is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
no_call ->
is_post_to_missing_resource(Req, State, 410)
end.
is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
allow_missing_post(Req, State, OnFalse);
is_post_to_missing_resource(Req, State, OnFalse) ->
respond(Req, State, OnFalse).
allow_missing_post(Req, State, OnFalse) ->
expect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse).
method(Req, State=#state{method= <<"DELETE">>}) ->
delete_resource(Req, State);
method(Req, State=#state{method= <<"PUT">>}) ->
is_conflict(Req, State);
method(Req, State=#state{method=Method})
when Method =:= <<"POST">>; Method =:= <<"PATCH">> ->
accept_resource(Req, State);
method(Req, State=#state{method=Method})
when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
set_resp_body_etag(Req, State);
method(Req, State) ->
multiple_choices(Req, State).
%% delete_resource/2 should start deleting the resource and return.
delete_resource(Req, State) ->
expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
%% delete_completed/2 indicates whether the resource has been deleted yet.
delete_completed(Req, State) ->
expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
is_conflict(Req, State) ->
expect(Req, State, is_conflict, false, fun accept_resource/2, 409).
%% content_types_accepted should return a list of media types and their
%% associated callback functions in the same format as content_types_provided.
%%
%% The callback will then be called and is expected to process the content
%% pushed to the resource in the request body.
%%
%% content_types_accepted SHOULD return a different list
%% for each HTTP method.
accept_resource(Req, State) ->
case call(Req, State, content_types_accepted) of
no_call ->
respond(Req, State, 415);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{CTA, Req2, HandlerState} ->
CTA2 = [normalize_content_types(P) || P <- CTA],
State2 = State#state{handler_state=HandlerState},
case cowboy_req:parse_header(<<"content-type">>, Req2) of
{ok, ContentType, Req3} ->
choose_content_type(Req3, State2, ContentType, CTA2);
{error, badarg} ->
respond(Req2, State2, 415)
end
end.
%% The special content type '*' will always match. It can be used as a
%% catch-all content type for accepting any kind of request content.
%% Note that because it will always match, it should be the last of the
%% list of content types, otherwise it'll shadow the ones following.
choose_content_type(Req, State, _ContentType, []) ->
respond(Req, State, 415);
choose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail])
when Accepted =:= '*'; Accepted =:= ContentType ->
process_content_type(Req, State, Fun);
%% The special parameter '*' will always match any kind of content type
%% parameters.
%% Note that because it will always match, it should be the last of the
%% list for specific content type, otherwise it'll shadow the ones following.
choose_content_type(Req, State, {Type, SubType, Param},
[{{Type, SubType, AcceptedParam}, Fun}|_Tail])
when AcceptedParam =:= '*'; AcceptedParam =:= Param ->
process_content_type(Req, State, Fun);
choose_content_type(Req, State, ContentType, [_Any|Tail]) ->
choose_content_type(Req, State, ContentType, Tail).
process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->
try case call(Req, State, Fun) of
{halt, Req2, HandlerState2} ->
terminate(Req2, State#state{handler_state=HandlerState2});
{true, Req2, HandlerState2} when Exists ->
State2 = State#state{handler_state=HandlerState2},
next(Req2, State2, fun has_resp_body/2);
{true, Req2, HandlerState2} ->
State2 = State#state{handler_state=HandlerState2},
next(Req2, State2, fun maybe_created/2);
{false, Req2, HandlerState2} ->
State2 = State#state{handler_state=HandlerState2},
respond(Req2, State2, 400);
{{true, ResURL}, Req2, HandlerState2} when Method =:= <<"POST">> ->
State2 = State#state{handler_state=HandlerState2},
Req3 = cowboy_req:set_resp_header(
<<"location">>, ResURL, Req2),
if
Exists -> respond(Req3, State2, 303);
true -> respond(Req3, State2, 201)
end
end catch Class:Reason = {case_clause, no_call} ->
error_terminate(Req, State, Class, Reason, Fun)
end.
%% If PUT was used then the resource has been created at the current URL.
%% Otherwise, if a location header has been set then the resource has been
%% created at a new URL. If not, send a 200 or 204 as expected from a
%% POST or PATCH request.
maybe_created(Req, State=#state{method= <<"PUT">>}) ->
respond(Req, State, 201);
maybe_created(Req, State) ->
case cowboy_req:has_resp_header(<<"location">>, Req) of
true -> respond(Req, State, 201);
false -> has_resp_body(Req, State)
end.
has_resp_body(Req, State) ->
case cowboy_req:has_resp_body(Req) of
true -> multiple_choices(Req, State);
false -> respond(Req, State, 204)
end.
%% Set the Etag header if any for the response provided.
set_resp_body_etag(Req, State) ->
try set_resp_etag(Req, State) of
{Req2, State2} ->
set_resp_body_last_modified(Req2, State2)
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, generate_etag)
end.
%% Set the Last-Modified header if any for the response provided.
set_resp_body_last_modified(Req, State) ->
try last_modified(Req, State) of
{LastModified, Req2, State2} ->
case LastModified of
LastModified when is_atom(LastModified) ->
set_resp_body_expires(Req2, State2);
LastModified ->
LastModifiedBin = cowboy_clock:rfc1123(LastModified),
Req3 = cowboy_req:set_resp_header(
<<"last-modified">>, LastModifiedBin, Req2),
set_resp_body_expires(Req3, State2)
end
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, last_modified)
end.
%% Set the Expires header if any for the response provided.
set_resp_body_expires(Req, State) ->
try set_resp_expires(Req, State) of
{Req2, State2} ->
set_resp_body(Req2, State2)
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, expires)
end.
%% Set the response headers and call the callback found using
%% content_types_provided/2 to obtain the request body and add
%% it to the response.
set_resp_body(Req, State=#state{content_type_a={_, Callback}}) ->
try case call(Req, State, Callback) of
{halt, Req2, HandlerState2} ->
terminate(Req2, State#state{handler_state=HandlerState2});
{Body, Req2, HandlerState2} ->
State2 = State#state{handler_state=HandlerState2},
Req3 = case Body of
{stream, StreamFun} ->
cowboy_req:set_resp_body_fun(StreamFun, Req2);
{stream, Len, StreamFun} ->
cowboy_req:set_resp_body_fun(Len, StreamFun, Req2);
{chunked, StreamFun} ->
cowboy_req:set_resp_body_fun(chunked, StreamFun, Req2);
_Contents ->
cowboy_req:set_resp_body(Body, Req2)
end,
multiple_choices(Req3, State2)
end catch Class:Reason = {case_clause, no_call} ->
error_terminate(Req, State, Class, Reason, Callback)
end.
multiple_choices(Req, State) ->
expect(Req, State, multiple_choices, false, 200, 300).
%% Response utility functions.
set_resp_etag(Req, State) ->
{Etag, Req2, State2} = generate_etag(Req, State),
case Etag of
undefined ->
{Req2, State2};
Etag ->
Req3 = cowboy_req:set_resp_header(
<<"etag">>, encode_etag(Etag), Req2),
{Req3, State2}
end.
-spec encode_etag({strong | weak, binary()}) -> iolist().
encode_etag({strong, Etag}) -> [$",Etag,$"];
encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
set_resp_expires(Req, State) ->
{Expires, Req2, State2} = expires(Req, State),
case Expires of
Expires when is_atom(Expires) ->
{Req2, State2};
Expires when is_binary(Expires) ->
Req3 = cowboy_req:set_resp_header(
<<"expires">>, Expires, Req2),
{Req3, State2};
Expires ->
ExpiresBin = cowboy_clock:rfc1123(Expires),
Req3 = cowboy_req:set_resp_header(
<<"expires">>, ExpiresBin, Req2),
{Req3, State2}
end.
%% Info retrieval. No logic.
generate_etag(Req, State=#state{etag=no_call}) ->
{undefined, Req, State};
generate_etag(Req, State=#state{etag=undefined}) ->
case unsafe_call(Req, State, generate_etag) of
no_call ->
{undefined, Req, State#state{etag=no_call}};
{Etag, Req2, HandlerState} when is_binary(Etag) ->
[Etag2] = cowboy_http:entity_tag_match(Etag),
{Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}};
{Etag, Req2, HandlerState} ->
{Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}}
end;
generate_etag(Req, State=#state{etag=Etag}) ->
{Etag, Req, State}.
last_modified(Req, State=#state{last_modified=no_call}) ->
{undefined, Req, State};
last_modified(Req, State=#state{last_modified=undefined}) ->
case unsafe_call(Req, State, last_modified) of
no_call ->
{undefined, Req, State#state{last_modified=no_call}};
{LastModified, Req2, HandlerState} ->
{LastModified, Req2, State#state{handler_state=HandlerState,
last_modified=LastModified}}
end;
last_modified(Req, State=#state{last_modified=LastModified}) ->
{LastModified, Req, State}.
expires(Req, State=#state{expires=no_call}) ->
{undefined, Req, State};
expires(Req, State=#state{expires=undefined}) ->
case unsafe_call(Req, State, expires) of
no_call ->
{undefined, Req, State#state{expires=no_call}};
{Expires, Req2, HandlerState} ->
{Expires, Req2, State#state{handler_state=HandlerState,
expires=Expires}}
end;
expires(Req, State=#state{expires=Expires}) ->
{Expires, Req, State}.
%% REST primitives.
expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
case call(Req, State, Callback) of
no_call ->
next(Req, State, OnTrue);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{Expected, Req2, HandlerState} ->
next(Req2, State#state{handler_state=HandlerState}, OnTrue);
{_Unexpected, Req2, HandlerState} ->
next(Req2, State#state{handler_state=HandlerState}, OnFalse)
end.
call(Req, State=#state{handler=Handler, handler_state=HandlerState},
Callback) ->
case erlang:function_exported(Handler, Callback, 2) of
true ->
try
Handler:Callback(Req, HandlerState)
catch Class:Reason ->
error_terminate(Req, State, Class, Reason, Callback)
end;
false ->
no_call
end.
unsafe_call(Req, #state{handler=Handler, handler_state=HandlerState},
Callback) ->
case erlang:function_exported(Handler, Callback, 2) of
true -> Handler:Callback(Req, HandlerState);
false -> no_call
end.
next(Req, State, Next) when is_function(Next) ->
Next(Req, State);
next(Req, State, StatusCode) when is_integer(StatusCode) ->
respond(Req, State, StatusCode).
respond(Req, State, StatusCode) ->
{ok, Req2} = cowboy_req:reply(StatusCode, Req),
terminate(Req2, State).
terminate(Req, State=#state{env=Env}) ->
rest_terminate(Req, State),
{ok, Req, [{result, ok}|Env]}.
error_terminate(Req, State=#state{handler=Handler, handler_state=HandlerState},
Class, Reason, Callback) ->
Stacktrace = erlang:get_stacktrace(),
rest_terminate(Req, State),
cowboy_req:maybe_reply(Stacktrace, Req),
erlang:Class([
{reason, Reason},
{mfa, {Handler, Callback, 2}},
{stacktrace, Stacktrace},
{req, cowboy_req:to_list(Req)},
{state, HandlerState}
]).
rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
case erlang:function_exported(Handler, rest_terminate, 2) of
true -> ok = Handler:rest_terminate(
cowboy_req:lock(Req), HandlerState);
false -> ok
end.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% Routing middleware.
%%
%% Resolve the handler to be used for the request based on the
%% routing information found in the <em>dispatch</em> environment value.
%% When found, the handler module and associated data are added to
%% the environment as the <em>handler</em> and <em>handler_opts</em> values
%% respectively.
%%
%% If the route cannot be found, processing stops with either
%% a 400 or a 404 reply.
-module(cowboy_router).
-behaviour(cowboy_middleware).
-export([compile/1]).
-export([execute/2]).
-type bindings() :: [{atom(), binary()}].
-type tokens() :: [binary()].
-export_type([bindings/0]).
-export_type([tokens/0]).
-type constraints() :: [{atom(), int}
| {atom(), function, fun ((binary()) -> true | {true, any()} | false)}].
-export_type([constraints/0]).
-type route_match() :: '_' | iodata().
-type route_path() :: {Path::route_match(), Handler::module(), Opts::any()}
| {Path::route_match(), constraints(), Handler::module(), Opts::any()}.
-type route_rule() :: {Host::route_match(), Paths::[route_path()]}
| {Host::route_match(), constraints(), Paths::[route_path()]}.
-type routes() :: [route_rule()].
-export_type([routes/0]).
-type dispatch_match() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].
-type dispatch_path() :: {dispatch_match(), module(), any()}.
-type dispatch_rule() :: {Host::dispatch_match(), Paths::[dispatch_path()]}.
-opaque dispatch_rules() :: [dispatch_rule()].
-export_type([dispatch_rules/0]).
-spec compile(routes()) -> dispatch_rules().
compile(Routes) ->
compile(Routes, []).
compile([], Acc) ->
lists:reverse(Acc);
compile([{Host, Paths}|Tail], Acc) ->
compile([{Host, [], Paths}|Tail], Acc);
compile([{HostMatch, Constraints, Paths}|Tail], Acc) ->
HostRules = case HostMatch of
'_' -> '_';
_ -> compile_host(HostMatch)
end,
PathRules = compile_paths(Paths, []),
Hosts = case HostRules of
'_' -> [{'_', Constraints, PathRules}];
_ -> [{R, Constraints, PathRules} || R <- HostRules]
end,
compile(Tail, Hosts ++ Acc).
compile_host(HostMatch) when is_list(HostMatch) ->
compile_host(list_to_binary(HostMatch));
compile_host(HostMatch) when is_binary(HostMatch) ->
compile_rules(HostMatch, $., [], [], <<>>).
compile_paths([], Acc) ->
lists:reverse(Acc);
compile_paths([{PathMatch, Handler, Opts}|Tail], Acc) ->
compile_paths([{PathMatch, [], Handler, Opts}|Tail], Acc);
compile_paths([{PathMatch, Constraints, Handler, Opts}|Tail], Acc)
when is_list(PathMatch) ->
compile_paths([{iolist_to_binary(PathMatch),
Constraints, Handler, Opts}|Tail], Acc);
compile_paths([{'_', Constraints, Handler, Opts}|Tail], Acc) ->
compile_paths(Tail, [{'_', Constraints, Handler, Opts}] ++ Acc);
compile_paths([{<< $/, PathMatch/binary >>, Constraints, Handler, Opts}|Tail],
Acc) ->
PathRules = compile_rules(PathMatch, $/, [], [], <<>>),
Paths = [{lists:reverse(R), Constraints, Handler, Opts} || R <- PathRules],
compile_paths(Tail, Paths ++ Acc);
compile_paths([{PathMatch, _, _, _}|_], _) ->
error({badarg, "The following route MUST begin with a slash: "
++ binary_to_list(PathMatch)}).
compile_rules(<<>>, _, Segments, Rules, <<>>) ->
[Segments|Rules];
compile_rules(<<>>, _, Segments, Rules, Acc) ->
[[Acc|Segments]|Rules];
compile_rules(<< S, Rest/binary >>, S, Segments, Rules, <<>>) ->
compile_rules(Rest, S, Segments, Rules, <<>>);
compile_rules(<< S, Rest/binary >>, S, Segments, Rules, Acc) ->
compile_rules(Rest, S, [Acc|Segments], Rules, <<>>);
compile_rules(<< $:, Rest/binary >>, S, Segments, Rules, <<>>) ->
{NameBin, Rest2} = compile_binding(Rest, S, <<>>),
Name = binary_to_atom(NameBin, utf8),
compile_rules(Rest2, S, Segments, Rules, Name);
compile_rules(<< $:, _/binary >>, _, _, _, _) ->
erlang:error(badarg);
compile_rules(<< $[, $., $., $., $], Rest/binary >>, S, Segments, Rules, Acc)
when Acc =:= <<>> ->
compile_rules(Rest, S, ['...'|Segments], Rules, Acc);
compile_rules(<< $[, $., $., $., $], Rest/binary >>, S, Segments, Rules, Acc) ->
compile_rules(Rest, S, ['...', Acc|Segments], Rules, Acc);
compile_rules(<< $[, S, Rest/binary >>, S, Segments, Rules, Acc) ->
compile_brackets(Rest, S, [Acc|Segments], Rules);
compile_rules(<< $[, Rest/binary >>, S, Segments, Rules, <<>>) ->
compile_brackets(Rest, S, Segments, Rules);
%% Open bracket in the middle of a segment.
compile_rules(<< $[, _/binary >>, _, _, _, _) ->
erlang:error(badarg);
%% Missing an open bracket.
compile_rules(<< $], _/binary >>, _, _, _, _) ->
erlang:error(badarg);
compile_rules(<< C, Rest/binary >>, S, Segments, Rules, Acc) ->
compile_rules(Rest, S, Segments, Rules, << Acc/binary, C >>).
%% Everything past $: until the segment separator ($. for hosts,
%% $/ for paths) or $[ or $] or end of binary is the binding name.
compile_binding(<<>>, _, <<>>) ->
erlang:error(badarg);
compile_binding(Rest = <<>>, _, Acc) ->
{Acc, Rest};
compile_binding(Rest = << C, _/binary >>, S, Acc)
when C =:= S; C =:= $[; C =:= $] ->
{Acc, Rest};
compile_binding(<< C, Rest/binary >>, S, Acc) ->
compile_binding(Rest, S, << Acc/binary, C >>).
compile_brackets(Rest, S, Segments, Rules) ->
{Bracket, Rest2} = compile_brackets_split(Rest, <<>>, 0),
Rules1 = compile_rules(Rest2, S, Segments, [], <<>>),
Rules2 = compile_rules(<< Bracket/binary, Rest2/binary >>,
S, Segments, [], <<>>),
Rules ++ Rules2 ++ Rules1.
%% Missing a close bracket.
compile_brackets_split(<<>>, _, _) ->
erlang:error(badarg);
%% Make sure we don't confuse the closing bracket we're looking for.
compile_brackets_split(<< C, Rest/binary >>, Acc, N) when C =:= $[ ->
compile_brackets_split(Rest, << Acc/binary, C >>, N + 1);
compile_brackets_split(<< C, Rest/binary >>, Acc, N) when C =:= $], N > 0 ->
compile_brackets_split(Rest, << Acc/binary, C >>, N - 1);
%% That's the right one.
compile_brackets_split(<< $], Rest/binary >>, Acc, 0) ->
{Acc, Rest};
compile_brackets_split(<< C, Rest/binary >>, Acc, N) ->
compile_brackets_split(Rest, << Acc/binary, C >>, N).
-spec execute(Req, Env)
-> {ok, Req, Env} | {error, 400 | 404, Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
execute(Req, Env) ->
{_, Dispatch} = lists:keyfind(dispatch, 1, Env),
[Host, Path] = cowboy_req:get([host, path], Req),
case match(Dispatch, Host, Path) of
{ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
Req2 = cowboy_req:set_bindings(HostInfo, PathInfo, Bindings, Req),
{ok, Req2, [{handler, Handler}, {handler_opts, HandlerOpts}|Env]};
{error, notfound, host} ->
{error, 400, Req};
{error, badrequest, path} ->
{error, 400, Req};
{error, notfound, path} ->
{error, 404, Req}
end.
%% Internal.
%% Match hostname tokens and path tokens against dispatch rules.
%%
%% It is typically used for matching tokens for the hostname and path of
%% the request against a global dispatch rule for your listener.
%%
%% Dispatch rules are a list of <em>{Hostname, PathRules}</em> tuples, with
%% <em>PathRules</em> being a list of <em>{Path, HandlerMod, HandlerOpts}</em>.
%%
%% <em>Hostname</em> and <em>Path</em> are match rules and can be either the
%% atom <em>'_'</em>, which matches everything, `<<"*">>', which match the
%% wildcard path, or a list of tokens.
%%
%% Each token can be either a binary, the atom <em>'_'</em>,
%% the atom '...' or a named atom. A binary token must match exactly,
%% <em>'_'</em> matches everything for a single token, <em>'...'</em> matches
%% everything for the rest of the tokens and a named atom will bind the
%% corresponding token value and return it.
%%
%% The list of hostname tokens is reversed before matching. For example, if
%% we were to match "www.ninenines.eu", we would first match "eu", then
%% "ninenines", then "www". This means that in the context of hostnames,
%% the <em>'...'</em> atom matches properly the lower levels of the domain
%% as would be expected.
%%
%% When a result is found, this function will return the handler module and
%% options found in the dispatch list, a key-value list of bindings and
%% the tokens that were matched by the <em>'...'</em> atom for both the
%% hostname and path.
-spec match(dispatch_rules(), Host::binary() | tokens(), Path::binary())
-> {ok, module(), any(), bindings(),
HostInfo::undefined | tokens(),
PathInfo::undefined | tokens()}
| {error, notfound, host} | {error, notfound, path}
| {error, badrequest, path}.
match([], _, _) ->
{error, notfound, host};
%% If the host is '_' then there can be no constraints.
match([{'_', [], PathMatchs}|_Tail], _, Path) ->
match_path(PathMatchs, undefined, Path, []);
match([{HostMatch, Constraints, PathMatchs}|Tail], Tokens, Path)
when is_list(Tokens) ->
case list_match(Tokens, HostMatch, []) of
false ->
match(Tail, Tokens, Path);
{true, Bindings, HostInfo} ->
HostInfo2 = case HostInfo of
undefined -> undefined;
_ -> lists:reverse(HostInfo)
end,
case check_constraints(Constraints, Bindings) of
{ok, Bindings2} ->
match_path(PathMatchs, HostInfo2, Path, Bindings2);
nomatch ->
match(Tail, Tokens, Path)
end
end;
match(Dispatch, Host, Path) ->
match(Dispatch, split_host(Host), Path).
-spec match_path([dispatch_path()],
HostInfo::undefined | tokens(), binary() | tokens(), bindings())
-> {ok, module(), any(), bindings(),
HostInfo::undefined | tokens(),
PathInfo::undefined | tokens()}
| {error, notfound, path} | {error, badrequest, path}.
match_path([], _, _, _) ->
{error, notfound, path};
%% If the path is '_' then there can be no constraints.
match_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
{ok, Handler, Opts, Bindings, HostInfo, undefined};
match_path([{<<"*">>, _Constraints, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
{ok, Handler, Opts, Bindings, HostInfo, undefined};
match_path([{PathMatch, Constraints, Handler, Opts}|Tail], HostInfo, Tokens,
Bindings) when is_list(Tokens) ->
case list_match(Tokens, PathMatch, Bindings) of
false ->
match_path(Tail, HostInfo, Tokens, Bindings);
{true, PathBinds, PathInfo} ->
case check_constraints(Constraints, PathBinds) of
{ok, PathBinds2} ->
{ok, Handler, Opts, PathBinds2, HostInfo, PathInfo};
nomatch ->
match_path(Tail, HostInfo, Tokens, Bindings)
end
end;
match_path(_Dispatch, _HostInfo, badrequest, _Bindings) ->
{error, badrequest, path};
match_path(Dispatch, HostInfo, Path, Bindings) ->
match_path(Dispatch, HostInfo, split_path(Path), Bindings).
check_constraints([], Bindings) ->
{ok, Bindings};
check_constraints([Constraint|Tail], Bindings) ->
Name = element(1, Constraint),
case lists:keyfind(Name, 1, Bindings) of
false ->
check_constraints(Tail, Bindings);
{_, Value} ->
case check_constraint(Constraint, Value) of
true ->
check_constraints(Tail, Bindings);
{true, Value2} ->
Bindings2 = lists:keyreplace(Name, 1, Bindings,
{Name, Value2}),
check_constraints(Tail, Bindings2);
false ->
nomatch
end
end.
check_constraint({_, int}, Value) ->
try {true, list_to_integer(binary_to_list(Value))}
catch _:_ -> false
end;
check_constraint({_, function, Fun}, Value) ->
Fun(Value).
-spec split_host(binary()) -> tokens().
split_host(Host) ->
split_host(Host, []).
split_host(Host, Acc) ->
case binary:match(Host, <<".">>) of
nomatch when Host =:= <<>> ->
Acc;
nomatch ->
[Host|Acc];
{Pos, _} ->
<< Segment:Pos/binary, _:8, Rest/bits >> = Host,
false = byte_size(Segment) == 0,
split_host(Rest, [Segment|Acc])
end.
%% Following RFC2396, this function may return path segments containing any
%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped
%% and part of a path segment.
-spec split_path(binary()) -> tokens().
split_path(<< $/, Path/bits >>) ->
split_path(Path, []);
split_path(_) ->
badrequest.
split_path(Path, Acc) ->
try
case binary:match(Path, <<"/">>) of
nomatch when Path =:= <<>> ->
lists:reverse([cow_qs:urldecode(S) || S <- Acc]);
nomatch ->
lists:reverse([cow_qs:urldecode(S) || S <- [Path|Acc]]);
{Pos, _} ->
<< Segment:Pos/binary, _:8, Rest/bits >> = Path,
split_path(Rest, [Segment|Acc])
end
catch
error:badarg ->
badrequest
end.
-spec list_match(tokens(), dispatch_match(), bindings())
-> {true, bindings(), undefined | tokens()} | false.
%% Atom '...' matches any trailing path, stop right now.
list_match(List, ['...'], Binds) ->
{true, Binds, List};
%% Atom '_' matches anything, continue.
list_match([_E|Tail], ['_'|TailMatch], Binds) ->
list_match(Tail, TailMatch, Binds);
%% Both values match, continue.
list_match([E|Tail], [E|TailMatch], Binds) ->
list_match(Tail, TailMatch, Binds);
%% Bind E to the variable name V and continue,
%% unless V was already defined and E isn't identical to the previous value.
list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->
case lists:keyfind(V, 1, Binds) of
{_, E} ->
list_match(Tail, TailMatch, Binds);
{_, _} ->
false;
false ->
list_match(Tail, TailMatch, [{V, E}|Binds])
end;
%% Match complete.
list_match([], [], Binds) ->
{true, Binds, undefined};
%% Values don't match, stop.
list_match(_List, _Match, _Binds) ->
false.
%% Tests.
-ifdef(TEST).
compile_test_() ->
Tests = [
%% Match any host and path.
{[{'_', [{'_', h, o}]}],
[{'_', [], [{'_', [], h, o}]}]},
{[{"cowboy.example.org",
[{"/", ha, oa}, {"/path/to/resource", hb, ob}]}],
[{[<<"org">>, <<"example">>, <<"cowboy">>], [], [
{[], [], ha, oa},
{[<<"path">>, <<"to">>, <<"resource">>], [], hb, ob}]}]},
{[{'_', [{"/path/to/resource/", h, o}]}],
[{'_', [], [{[<<"path">>, <<"to">>, <<"resource">>], [], h, o}]}]},
% Cyrillic from a latin1 encoded file.
{[{'_', [{[47,208,191,209,131,209,130,209,140,47,208,186,47,209,128,
208,181,209,129,209,131,209,128,209,129,209,131,47], h, o}]}],
[{'_', [], [{[<<208,191,209,131,209,130,209,140>>, <<208,186>>,
<<209,128,208,181,209,129,209,131,209,128,209,129,209,131>>],
[], h, o}]}]},
{[{"cowboy.example.org.", [{'_', h, o}]}],
[{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
{[{".cowboy.example.org", [{'_', h, o}]}],
[{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
% Cyrillic from a latin1 encoded file.
{[{[208,189,208,181,208,186,208,184,208,185,46,209,129,208,176,
208,185,209,130,46,209,128,209,132,46], [{'_', h, o}]}],
[{[<<209,128,209,132>>, <<209,129,208,176,208,185,209,130>>,
<<208,189,208,181,208,186,208,184,208,185>>],
[], [{'_', [], h, o}]}]},
{[{":subdomain.example.org", [{"/hats/:name/prices", h, o}]}],
[{[<<"org">>, <<"example">>, subdomain], [], [
{[<<"hats">>, name, <<"prices">>], [], h, o}]}]},
{[{"ninenines.:_", [{"/hats/:_", h, o}]}],
[{['_', <<"ninenines">>], [], [{[<<"hats">>, '_'], [], h, o}]}]},
{[{"[www.]ninenines.eu",
[{"/horses", h, o}, {"/hats/[page/:number]", h, o}]}], [
{[<<"eu">>, <<"ninenines">>], [], [
{[<<"horses">>], [], h, o},
{[<<"hats">>], [], h, o},
{[<<"hats">>, <<"page">>, number], [], h, o}]},
{[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
{[<<"horses">>], [], h, o},
{[<<"hats">>], [], h, o},
{[<<"hats">>, <<"page">>, number], [], h, o}]}]},
{[{'_', [{"/hats/[page/[:number]]", h, o}]}], [{'_', [], [
{[<<"hats">>], [], h, o},
{[<<"hats">>, <<"page">>], [], h, o},
{[<<"hats">>, <<"page">>, number], [], h, o}]}]},
{[{"[...]ninenines.eu", [{"/hats/[...]", h, o}]}],
[{[<<"eu">>, <<"ninenines">>, '...'], [], [
{[<<"hats">>, '...'], [], h, o}]}]}
],
[{lists:flatten(io_lib:format("~p", [Rt])),
fun() -> Rs = compile(Rt) end} || {Rt, Rs} <- Tests].
split_host_test_() ->
Tests = [
{<<"">>, []},
{<<"*">>, [<<"*">>]},
{<<"cowboy.ninenines.eu">>,
[<<"eu">>, <<"ninenines">>, <<"cowboy">>]},
{<<"ninenines.eu">>,
[<<"eu">>, <<"ninenines">>]},
{<<"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z">>,
[<<"z">>, <<"y">>, <<"x">>, <<"w">>, <<"v">>, <<"u">>, <<"t">>,
<<"s">>, <<"r">>, <<"q">>, <<"p">>, <<"o">>, <<"n">>, <<"m">>,
<<"l">>, <<"k">>, <<"j">>, <<"i">>, <<"h">>, <<"g">>, <<"f">>,
<<"e">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>]}
],
[{H, fun() -> R = split_host(H) end} || {H, R} <- Tests].
split_path_test_() ->
Tests = [
{<<"/">>, []},
{<<"/extend//cowboy">>, [<<"extend">>, <<>>, <<"cowboy">>]},
{<<"/users">>, [<<"users">>]},
{<<"/users/42/friends">>, [<<"users">>, <<"42">>, <<"friends">>]},
{<<"/users/a+b/c%21d">>, [<<"users">>, <<"a b">>, <<"c!d">>]}
],
[{P, fun() -> R = split_path(P) end} || {P, R} <- Tests].
match_test_() ->
Dispatch = [
{[<<"eu">>, <<"ninenines">>, '_', <<"www">>], [], [
{[<<"users">>, '_', <<"mails">>], [], match_any_subdomain_users, []}
]},
{[<<"eu">>, <<"ninenines">>], [], [
{[<<"users">>, id, <<"friends">>], [], match_extend_users_friends, []},
{'_', [], match_extend, []}
]},
{[var, <<"ninenines">>], [], [
{[<<"threads">>, var], [], match_duplicate_vars,
[we, {expect, two}, var, here]}
]},
{[ext, <<"erlang">>], [], [
{'_', [], match_erlang_ext, []}
]},
{'_', [], [
{[<<"users">>, id, <<"friends">>], [], match_users_friends, []},
{'_', [], match_any, []}
]}
],
Tests = [
{<<"any">>, <<"/">>, {ok, match_any, [], []}},
{<<"www.any.ninenines.eu">>, <<"/users/42/mails">>,
{ok, match_any_subdomain_users, [], []}},
{<<"www.ninenines.eu">>, <<"/users/42/mails">>,
{ok, match_any, [], []}},
{<<"www.ninenines.eu">>, <<"/">>,
{ok, match_any, [], []}},
{<<"www.any.ninenines.eu">>, <<"/not_users/42/mails">>,
{error, notfound, path}},
{<<"ninenines.eu">>, <<"/">>,
{ok, match_extend, [], []}},
{<<"ninenines.eu">>, <<"/users/42/friends">>,
{ok, match_extend_users_friends, [], [{id, <<"42">>}]}},
{<<"erlang.fr">>, '_',
{ok, match_erlang_ext, [], [{ext, <<"fr">>}]}},
{<<"any">>, <<"/users/444/friends">>,
{ok, match_users_friends, [], [{id, <<"444">>}]}}
],
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
{ok, Handler, Opts, Binds, undefined, undefined}
= match(Dispatch, H, P)
end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].
match_info_test_() ->
Dispatch = [
{[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
{[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], [], match_path, []}
]},
{[<<"eu">>, <<"ninenines">>, '...'], [], [
{'_', [], match_any, []}
]},
% Cyrillic from a latin1 encoded file.
{[<<209,128,209,132>>, <<209,129,208,176,208,185,209,130>>], [], [
{[<<208,191,209,131,209,130,209,140>>, '...'], [], match_path, []}
]}
],
Tests = [
{<<"ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [], undefined}},
{<<"bugs.ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [<<"bugs">>], undefined}},
{<<"cowboy.bugs.ninenines.eu">>, <<"/">>,
{ok, match_any, [], [], [<<"cowboy">>, <<"bugs">>], undefined}},
{<<"www.ninenines.eu">>, <<"/pathinfo/is/next">>,
{ok, match_path, [], [], undefined, []}},
{<<"www.ninenines.eu">>, <<"/pathinfo/is/next/path_info">>,
{ok, match_path, [], [], undefined, [<<"path_info">>]}},
{<<"www.ninenines.eu">>, <<"/pathinfo/is/next/foo/bar">>,
{ok, match_path, [], [], undefined, [<<"foo">>, <<"bar">>]}},
% Cyrillic from a latin1 encoded file.
{<<209,129,208,176,208,185,209,130,46,209,128,209,132>>,
<<47,208,191,209,131,209,130,209,140,47,208,180,208,190,208,188,208,190,208,185>>,
{ok, match_path, [], [], undefined, [<<208,180,208,190,208,188,208,190,208,185>>]}}
],
[{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
R = match(Dispatch, H, P)
end} || {H, P, R} <- Tests].
match_constraints_test() ->
Dispatch = [{'_', [],
[{[<<"path">>, value], [{value, int}], match, []}]}],
{ok, _, [], [{value, 123}], _, _} = match(Dispatch,
<<"ninenines.eu">>, <<"/path/123">>),
{ok, _, [], [{value, 123}], _, _} = match(Dispatch,
<<"ninenines.eu">>, <<"/path/123/">>),
{error, notfound, path} = match(Dispatch,
<<"ninenines.eu">>, <<"/path/NaN/">>),
Dispatch2 = [{'_', [],
[{[<<"path">>, username], [{username, function,
fun(Value) -> Value =:= cowboy_bstr:to_lower(Value) end}],
match, []}]}],
{ok, _, [], [{username, <<"essen">>}], _, _} = match(Dispatch2,
<<"ninenines.eu">>, <<"/path/essen">>),
{error, notfound, path} = match(Dispatch2,
<<"ninenines.eu">>, <<"/path/ESSEN">>),
ok.
match_same_bindings_test() ->
Dispatch = [{[same, same], [], [{'_', [], match, []}]}],
{ok, _, [], [{same, <<"eu">>}], _, _} = match(Dispatch,
<<"eu.eu">>, <<"/">>),
{error, notfound, host} = match(Dispatch,
<<"ninenines.eu">>, <<"/">>),
Dispatch2 = [{[<<"eu">>, <<"ninenines">>, user], [],
[{[<<"path">>, user], [], match, []}]}],
{ok, _, [], [{user, <<"essen">>}], _, _} = match(Dispatch2,
<<"essen.ninenines.eu">>, <<"/path/essen">>),
{ok, _, [], [{user, <<"essen">>}], _, _} = match(Dispatch2,
<<"essen.ninenines.eu">>, <<"/path/essen/">>),
{error, notfound, path} = match(Dispatch2,
<<"essen.ninenines.eu">>, <<"/path/notessen">>),
Dispatch3 = [{'_', [], [{[same, same], [], match, []}]}],
{ok, _, [], [{same, <<"path">>}], _, _} = match(Dispatch3,
<<"ninenines.eu">>, <<"/path/path">>),
{error, notfound, path} = match(Dispatch3,
<<"ninenines.eu">>, <<"/path/to">>),
ok.
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_spdy).
%% API.
-export([start_link/4]).
%% Internal.
-export([init/5]).
-export([system_continue/3]).
-export([system_terminate/4]).
-export([system_code_change/4]).
%% Internal request process.
-export([request_init/11]).
-export([resume/5]).
-export([reply/4]).
-export([stream_reply/3]).
-export([stream_data/2]).
-export([stream_close/1]).
%% Internal transport functions.
-export([name/0]).
-export([messages/0]).
-export([recv/3]).
-export([send/2]).
-export([sendfile/2]).
-export([setopts/2]).
-type streamid() :: non_neg_integer().
-type socket() :: {pid(), streamid()}.
-record(child, {
streamid :: streamid(),
pid :: pid(),
input = nofin :: fin | nofin,
in_buffer = <<>> :: binary(),
is_recv = false :: false | {active, socket(), pid()}
| {passive, socket(), pid(), non_neg_integer(), reference()},
output = nofin :: fin | nofin
}).
-record(state, {
parent = undefined :: pid(),
socket,
transport,
buffer = <<>> :: binary(),
middlewares,
env,
onrequest,
onresponse,
peer,
zdef,
zinf,
last_streamid = 0 :: streamid(),
children = [] :: [#child{}]
}).
-type opts() :: [{env, cowboy_middleware:env()}
| {middlewares, [module()]}
| {onrequest, cowboy:onrequest_fun()}
| {onresponse, cowboy:onresponse_fun()}].
-export_type([opts/0]).
%% API.
-spec start_link(any(), inet:socket(), module(), any()) -> {ok, pid()}.
start_link(Ref, Socket, Transport, Opts) ->
proc_lib:start_link(?MODULE, init,
[self(), Ref, Socket, Transport, Opts]).
%% Internal.
%% Faster alternative to proplists:get_value/3.
get_value(Key, Opts, Default) ->
case lists:keyfind(Key, 1, Opts) of
{_, Value} -> Value;
_ -> Default
end.
-spec init(pid(), ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Parent, Ref, Socket, Transport, Opts) ->
process_flag(trap_exit, true),
ok = proc_lib:init_ack(Parent, {ok, self()}),
{ok, Peer} = Transport:peername(Socket),
Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
Env = [{listener, Ref}|get_value(env, Opts, [])],
OnRequest = get_value(onrequest, Opts, undefined),
OnResponse = get_value(onresponse, Opts, undefined),
Zdef = cow_spdy:deflate_init(),
Zinf = cow_spdy:inflate_init(),
ok = ranch:accept_ack(Ref),
loop(#state{parent=Parent, socket=Socket, transport=Transport,
middlewares=Middlewares, env=Env, onrequest=OnRequest,
onresponse=OnResponse, peer=Peer, zdef=Zdef, zinf=Zinf}).
loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
buffer=Buffer, children=Children}) ->
{OK, Closed, Error} = Transport:messages(),
Transport:setopts(Socket, [{active, once}]),
receive
{OK, Socket, Data} ->
parse_frame(State, << Buffer/binary, Data/binary >>);
{Closed, Socket} ->
terminate(State);
{Error, Socket, _Reason} ->
terminate(State);
{recv, FromSocket = {Pid, StreamID}, FromPid, Length, Timeout}
when Pid =:= self() ->
Child = #child{in_buffer=InBuffer, is_recv=false}
= get_child(StreamID, State),
if
Length =:= 0, InBuffer =/= <<>> ->
FromPid ! {recv, FromSocket, {ok, InBuffer}},
loop(replace_child(Child#child{in_buffer= <<>>}, State));
byte_size(InBuffer) >= Length ->
<< Data:Length/binary, Rest/binary >> = InBuffer,
FromPid ! {recv, FromSocket, {ok, Data}},
loop(replace_child(Child#child{in_buffer=Rest}, State));
true ->
TRef = erlang:send_after(Timeout, self(),
{recv_timeout, FromSocket}),
loop(replace_child(Child#child{
is_recv={passive, FromSocket, FromPid, Length, TRef}},
State))
end;
{recv_timeout, {Pid, StreamID}}
when Pid =:= self() ->
Child = #child{is_recv={passive, FromSocket, FromPid, _, _}}
= get_child(StreamID, State),
FromPid ! {recv, FromSocket, {error, timeout}},
loop(replace_child(Child, State));
{reply, {Pid, StreamID}, Status, Headers}
when Pid =:= self() ->
Child = #child{output=nofin} = get_child(StreamID, State),
syn_reply(State, StreamID, true, Status, Headers),
loop(replace_child(Child#child{output=fin}, State));
{reply, {Pid, StreamID}, Status, Headers, Body}
when Pid =:= self() ->
Child = #child{output=nofin} = get_child(StreamID, State),
syn_reply(State, StreamID, false, Status, Headers),
data(State, StreamID, true, Body),
loop(replace_child(Child#child{output=fin}, State));
{stream_reply, {Pid, StreamID}, Status, Headers}
when Pid =:= self() ->
#child{output=nofin} = get_child(StreamID, State),
syn_reply(State, StreamID, false, Status, Headers),
loop(State);
{stream_data, {Pid, StreamID}, Data}
when Pid =:= self() ->
#child{output=nofin} = get_child(StreamID, State),
data(State, StreamID, false, Data),
loop(State);
{stream_close, {Pid, StreamID}}
when Pid =:= self() ->
Child = #child{output=nofin} = get_child(StreamID, State),
data(State, StreamID, true, <<>>),
loop(replace_child(Child#child{output=fin}, State));
{sendfile, {Pid, StreamID}, Filepath}
when Pid =:= self() ->
Child = #child{output=nofin} = get_child(StreamID, State),
data_from_file(State, StreamID, Filepath),
loop(replace_child(Child#child{output=fin}, State));
{active, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() ->
Child = #child{in_buffer=InBuffer, is_recv=false}
= get_child(StreamID, State),
case InBuffer of
<<>> ->
loop(replace_child(Child#child{
is_recv={active, FromSocket, FromPid}}, State));
_ ->
FromPid ! {spdy, FromSocket, InBuffer},
loop(replace_child(Child#child{in_buffer= <<>>}, State))
end;
{passive, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() ->
Child = #child{is_recv=IsRecv} = get_child(StreamID, State),
%% Make sure we aren't in the middle of a recv call.
case IsRecv of false -> ok; {active, FromSocket, FromPid} -> ok end,
loop(replace_child(Child#child{is_recv=false}, State));
{'EXIT', Parent, Reason} ->
exit(Reason);
{'EXIT', Pid, _} ->
%% @todo Report the error if any.
loop(delete_child(Pid, State));
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State);
%% Calls from the supervisor module.
{'$gen_call', {To, Tag}, which_children} ->
Workers = [{?MODULE, Pid, worker, [?MODULE]}
|| #child{pid=Pid} <- Children],
To ! {Tag, Workers},
loop(State);
{'$gen_call', {To, Tag}, count_children} ->
NbChildren = length(Children),
Counts = [{specs, 1}, {active, NbChildren},
{supervisors, 0}, {workers, NbChildren}],
To ! {Tag, Counts},
loop(State);
{'$gen_call', {To, Tag}, _} ->
To ! {Tag, {error, ?MODULE}},
loop(State)
after 60000 ->
goaway(State, ok),
terminate(State)
end.
-spec system_continue(_, _, #state{}) -> ok.
system_continue(_, _, State) ->
loop(State).
-spec system_terminate(any(), _, _, _) -> no_return().
system_terminate(Reason, _, _, _) ->
exit(Reason).
-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::#state{}.
system_code_change(Misc, _, _, _) ->
{ok, Misc}.
parse_frame(State=#state{zinf=Zinf}, Data) ->
case cow_spdy:split(Data) of
{true, Frame, Rest} ->
P = cow_spdy:parse(Frame, Zinf),
case handle_frame(State#state{buffer = Rest}, P) of
error ->
terminate(State);
State2 ->
parse_frame(State2, Rest)
end;
false ->
loop(State#state{buffer=Data})
end.
%% FLAG_UNIDIRECTIONAL can only be set by the server.
handle_frame(State, {syn_stream, StreamID, _, _, true,
_, _, _, _, _, _, _}) ->
rst_stream(State, StreamID, protocol_error),
State;
%% We do not support Associated-To-Stream-ID.
handle_frame(State, {syn_stream, StreamID, AssocToStreamID,
_, _, _, _, _, _, _, _, _}) when AssocToStreamID =/= 0 ->
rst_stream(State, StreamID, internal_error),
State;
%% SYN_STREAM.
%%
%% Erlang does not allow us to control the priority of processes
%% so we ignore that value entirely.
handle_frame(State=#state{middlewares=Middlewares, env=Env,
onrequest=OnRequest, onresponse=OnResponse, peer=Peer},
{syn_stream, StreamID, _, IsFin, _, _,
Method, _, Host, Path, Version, Headers}) ->
Pid = spawn_link(?MODULE, request_init, [
{self(), StreamID}, Peer, OnRequest, OnResponse,
Env, Middlewares, Method, Host, Path, Version, Headers
]),
new_child(State, StreamID, Pid, IsFin);
%% RST_STREAM.
handle_frame(State, {rst_stream, StreamID, Status}) ->
error_logger:error_msg("Received RST_STREAM frame ~p ~p",
[StreamID, Status]),
%% @todo Stop StreamID.
State;
%% PING initiated by the server; ignore, we don't send any.
handle_frame(State, {ping, PingID}) when PingID rem 2 =:= 0 ->
error_logger:error_msg("Ignored PING control frame: ~p~n", [PingID]),
State;
%% PING initiated by the client; send it back.
handle_frame(State=#state{socket=Socket, transport=Transport},
{ping, PingID}) ->
Transport:send(Socket, cow_spdy:ping(PingID)),
State;
%% Data received for a stream.
handle_frame(State, {data, StreamID, IsFin, Data}) ->
Child = #child{input=nofin, in_buffer=Buffer, is_recv=IsRecv}
= get_child(StreamID, State),
Data2 = << Buffer/binary, Data/binary >>,
IsFin2 = if IsFin -> fin; true -> nofin end,
Child2 = case IsRecv of
{active, FromSocket, FromPid} ->
FromPid ! {spdy, FromSocket, Data},
Child#child{input=IsFin2, is_recv=false};
{passive, FromSocket, FromPid, 0, TRef} ->
FromPid ! {recv, FromSocket, {ok, Data2}},
cancel_recv_timeout(StreamID, TRef),
Child#child{input=IsFin2, in_buffer= <<>>, is_recv=false};
{passive, FromSocket, FromPid, Length, TRef}
when byte_size(Data2) >= Length ->
<< Data3:Length/binary, Rest/binary >> = Data2,
FromPid ! {recv, FromSocket, {ok, Data3}},
cancel_recv_timeout(StreamID, TRef),
Child#child{input=IsFin2, in_buffer=Rest, is_recv=false};
_ ->
Child#child{input=IsFin2, in_buffer=Data2}
end,
replace_child(Child2, State);
%% General error, can't recover.
handle_frame(State, {error, badprotocol}) ->
goaway(State, protocol_error),
error;
%% Ignore all other frames for now.
handle_frame(State, Frame) ->
error_logger:error_msg("Ignored frame ~p", [Frame]),
State.
cancel_recv_timeout(StreamID, TRef) ->
_ = erlang:cancel_timer(TRef),
receive
{recv_timeout, {Pid, StreamID}}
when Pid =:= self() ->
ok
after 0 ->
ok
end.
%% @todo We must wait for the children to finish here,
%% but only up to N milliseconds. Then we shutdown.
terminate(_State) ->
ok.
syn_reply(#state{socket=Socket, transport=Transport, zdef=Zdef},
StreamID, IsFin, Status, Headers) ->
Transport:send(Socket, cow_spdy:syn_reply(Zdef, StreamID, IsFin,
Status, <<"HTTP/1.1">>, Headers)).
rst_stream(#state{socket=Socket, transport=Transport}, StreamID, Status) ->
Transport:send(Socket, cow_spdy:rst_stream(StreamID, Status)).
goaway(#state{socket=Socket, transport=Transport, last_streamid=LastStreamID},
Status) ->
Transport:send(Socket, cow_spdy:goaway(LastStreamID, Status)).
data(#state{socket=Socket, transport=Transport}, StreamID, IsFin, Data) ->
Transport:send(Socket, cow_spdy:data(StreamID, IsFin, Data)).
data_from_file(#state{socket=Socket, transport=Transport},
StreamID, Filepath) ->
{ok, IoDevice} = file:open(Filepath, [read, binary, raw]),
data_from_file(Socket, Transport, StreamID, IoDevice).
data_from_file(Socket, Transport, StreamID, IoDevice) ->
case file:read(IoDevice, 16#1fff) of
eof ->
_ = Transport:send(Socket, cow_spdy:data(StreamID, true, <<>>)),
ok;
{ok, Data} ->
case Transport:send(Socket, cow_spdy:data(StreamID, false, Data)) of
ok ->
data_from_file(Socket, Transport, StreamID, IoDevice);
{error, _} ->
ok
end
end.
%% Children.
new_child(State=#state{children=Children}, StreamID, Pid, IsFin) ->
IsFin2 = if IsFin -> fin; true -> nofin end,
State#state{last_streamid=StreamID,
children=[#child{streamid=StreamID,
pid=Pid, input=IsFin2}|Children]}.
get_child(StreamID, #state{children=Children}) ->
lists:keyfind(StreamID, #child.streamid, Children).
replace_child(Child=#child{streamid=StreamID},
State=#state{children=Children}) ->
Children2 = lists:keyreplace(StreamID, #child.streamid, Children, Child),
State#state{children=Children2}.
delete_child(Pid, State=#state{children=Children}) ->
Children2 = lists:keydelete(Pid, #child.pid, Children),
State#state{children=Children2}.
%% Request process.
-spec request_init(socket(), {inet:ip_address(), inet:port_number()},
cowboy:onrequest_fun(), cowboy:onresponse_fun(),
cowboy_middleware:env(), [module()],
binary(), binary(), binary(), binary(), [{binary(), binary()}])
-> ok.
request_init(FakeSocket, Peer, OnRequest, OnResponse,
Env, Middlewares, Method, Host, Path, Version, Headers) ->
{Host2, Port} = cow_http:parse_fullhost(Host),
{Path2, Qs} = cow_http:parse_fullpath(Path),
Version2 = cow_http:parse_version(Version),
Req = cowboy_req:new(FakeSocket, ?MODULE, Peer,
Method, Path2, Qs, Version2, Headers,
Host2, Port, <<>>, true, false, OnResponse),
case OnRequest of
undefined ->
execute(Req, Env, Middlewares);
_ ->
Req2 = OnRequest(Req),
case cowboy_req:get(resp_state, Req2) of
waiting -> execute(Req2, Env, Middlewares);
_ -> ok
end
end.
-spec execute(cowboy_req:req(), cowboy_middleware:env(), [module()])
-> ok.
execute(Req, _, []) ->
cowboy_req:ensure_response(Req, 204);
execute(Req, Env, [Middleware|Tail]) ->
case Middleware:execute(Req, Env) of
{ok, Req2, Env2} ->
execute(Req2, Env2, Tail);
{suspend, Module, Function, Args} ->
erlang:hibernate(?MODULE, resume,
[Env, Tail, Module, Function, Args]);
{halt, Req2} ->
cowboy_req:ensure_response(Req2, 204);
{error, Status, Req2} ->
cowboy_req:reply(Status, Req2)
end.
-spec resume(cowboy_middleware:env(), [module()],
module(), module(), [any()]) -> ok.
resume(Env, Tail, Module, Function, Args) ->
case apply(Module, Function, Args) of
{ok, Req2, Env2} ->
execute(Req2, Env2, Tail);
{suspend, Module2, Function2, Args2} ->
erlang:hibernate(?MODULE, resume,
[Env, Tail, Module2, Function2, Args2]);
{halt, Req2} ->
cowboy_req:ensure_response(Req2, 204);
{error, Status, Req2} ->
cowboy_req:reply(Status, Req2)
end.
%% Reply functions used by cowboy_req.
-spec reply(socket(), binary(), cowboy:http_headers(), iodata()) -> ok.
reply(Socket = {Pid, _}, Status, Headers, Body) ->
_ = case iolist_size(Body) of
0 -> Pid ! {reply, Socket, Status, Headers};
_ -> Pid ! {reply, Socket, Status, Headers, Body}
end,
ok.
-spec stream_reply(socket(), binary(), cowboy:http_headers()) -> ok.
stream_reply(Socket = {Pid, _}, Status, Headers) ->
_ = Pid ! {stream_reply, Socket, Status, Headers},
ok.
-spec stream_data(socket(), iodata()) -> ok.
stream_data(Socket = {Pid, _}, Data) ->
_ = Pid ! {stream_data, Socket, Data},
ok.
-spec stream_close(socket()) -> ok.
stream_close(Socket = {Pid, _}) ->
_ = Pid ! {stream_close, Socket},
ok.
%% Internal transport functions.
-spec name() -> spdy.
name() ->
spdy.
-spec messages() -> {spdy, spdy_closed, spdy_error}.
messages() ->
{spdy, spdy_closed, spdy_error}.
-spec recv(socket(), non_neg_integer(), timeout())
-> {ok, binary()} | {error, timeout}.
recv(Socket = {Pid, _}, Length, Timeout) ->
_ = Pid ! {recv, Socket, self(), Length, Timeout},
receive
{recv, Socket, Ret} ->
Ret
end.
-spec send(socket(), iodata()) -> ok.
send(Socket, Data) ->
stream_data(Socket, Data).
%% We don't wait for the result of the actual sendfile call,
%% therefore we can't know how much was actually sent.
%% This isn't a problem as we don't use this value in Cowboy.
-spec sendfile(socket(), file:name_all()) -> {ok, undefined}.
sendfile(Socket = {Pid, _}, Filepath) ->
_ = Pid ! {sendfile, Socket, Filepath},
{ok, undefined}.
-spec setopts({pid(), _}, list()) -> ok.
setopts(Socket = {Pid, _}, [{active, once}]) ->
_ = Pid ! {active, Socket, self()},
ok;
setopts(Socket = {Pid, _}, [{active, false}]) ->
_ = Pid ! {passive, Socket, self()},
ok.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
%% Copyright (c) 2011, Magnus Klaar <magnus.klaar@gmail.com>
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_static).
-export([init/3]).
-export([rest_init/2]).
-export([malformed_request/2]).
-export([forbidden/2]).
-export([content_types_provided/2]).
-export([resource_exists/2]).
-export([last_modified/2]).
-export([generate_etag/2]).
-export([get_file/2]).
-type extra_etag() :: {etag, module(), function()} | {etag, false}.
-type extra_mimetypes() :: {mimetypes, module(), function()}
| {mimetypes, binary() | {binary(), binary(), [{binary(), binary()}]}}.
-type extra() :: [extra_etag() | extra_mimetypes()].
-type opts() :: {file | dir, string() | binary()}
| {file | dir, string() | binary(), extra()}
| {priv_file | priv_dir, atom(), string() | binary()}
| {priv_file | priv_dir, atom(), string() | binary(), extra()}.
-export_type([opts/0]).
-include_lib("kernel/include/file.hrl").
-type state() :: {binary(), {ok, #file_info{}} | {error, atom()}, extra()}.
-spec init(_, _, _) -> {upgrade, protocol, cowboy_rest}.
init(_, _, _) ->
{upgrade, protocol, cowboy_rest}.
%% Resolve the file that will be sent and get its file information.
%% If the handler is configured to manage a directory, check that the
%% requested file is inside the configured directory.
-spec rest_init(Req, opts())
-> {ok, Req, error | state()}
when Req::cowboy_req:req().
rest_init(Req, {Name, Path}) ->
rest_init_opts(Req, {Name, Path, []});
rest_init(Req, {Name, App, Path})
when Name =:= priv_file; Name =:= priv_dir ->
rest_init_opts(Req, {Name, App, Path, []});
rest_init(Req, Opts) ->
rest_init_opts(Req, Opts).
rest_init_opts(Req, {priv_file, App, Path, Extra}) ->
rest_init_info(Req, absname(priv_path(App, Path)), Extra);
rest_init_opts(Req, {file, Path, Extra}) ->
rest_init_info(Req, absname(Path), Extra);
rest_init_opts(Req, {priv_dir, App, Path, Extra}) ->
rest_init_dir(Req, priv_path(App, Path), Extra);
rest_init_opts(Req, {dir, Path, Extra}) ->
rest_init_dir(Req, Path, Extra).
priv_path(App, Path) ->
case code:priv_dir(App) of
{error, bad_name} ->
error({badarg, "Can't resolve the priv_dir of application "
++ atom_to_list(App)});
PrivDir when is_list(Path) ->
PrivDir ++ "/" ++ Path;
PrivDir when is_binary(Path) ->
<< (list_to_binary(PrivDir))/binary, $/, Path/binary >>
end.
absname(Path) when is_list(Path) ->
filename:absname(list_to_binary(Path));
absname(Path) when is_binary(Path) ->
filename:absname(Path).
rest_init_dir(Req, Path, Extra) when is_list(Path) ->
rest_init_dir(Req, list_to_binary(Path), Extra);
rest_init_dir(Req, Path, Extra) ->
Dir = fullpath(filename:absname(Path)),
{PathInfo, Req2} = cowboy_req:path_info(Req),
Filepath = filename:join([Dir|PathInfo]),
Len = byte_size(Dir),
case fullpath(Filepath) of
<< Dir:Len/binary, $/, _/binary >> ->
rest_init_info(Req2, Filepath, Extra);
_ ->
{ok, Req2, error}
end.
fullpath(Path) ->
fullpath(filename:split(Path), []).
fullpath([], Acc) ->
filename:join(lists:reverse(Acc));
fullpath([<<".">>|Tail], Acc) ->
fullpath(Tail, Acc);
fullpath([<<"..">>|Tail], Acc=[_]) ->
fullpath(Tail, Acc);
fullpath([<<"..">>|Tail], [_|Acc]) ->
fullpath(Tail, Acc);
fullpath([Segment|Tail], Acc) ->
fullpath(Tail, [Segment|Acc]).
rest_init_info(Req, Path, Extra) ->
Info = file:read_file_info(Path, [{time, universal}]),
{ok, Req, {Path, Info, Extra}}.
-ifdef(TEST).
fullpath_test_() ->
Tests = [
{<<"/home/cowboy">>, <<"/home/cowboy">>},
{<<"/home/cowboy">>, <<"/home/cowboy/">>},
{<<"/home/cowboy">>, <<"/home/cowboy/./">>},
{<<"/home/cowboy">>, <<"/home/cowboy/./././././.">>},
{<<"/home/cowboy">>, <<"/home/cowboy/abc/..">>},
{<<"/home/cowboy">>, <<"/home/cowboy/abc/../">>},
{<<"/home/cowboy">>, <<"/home/cowboy/abc/./../.">>},
{<<"/">>, <<"/home/cowboy/../../../../../..">>},
{<<"/etc/passwd">>, <<"/home/cowboy/../../etc/passwd">>}
],
[{P, fun() -> R = fullpath(P) end} || {R, P} <- Tests].
good_path_check_test_() ->
Tests = [
<<"/home/cowboy/file">>,
<<"/home/cowboy/file/">>,
<<"/home/cowboy/./file">>,
<<"/home/cowboy/././././././file">>,
<<"/home/cowboy/abc/../file">>,
<<"/home/cowboy/abc/../file">>,
<<"/home/cowboy/abc/./.././file">>
],
[{P, fun() ->
case fullpath(P) of
<< "/home/cowboy/", _/binary >> -> ok
end
end} || P <- Tests].
bad_path_check_test_() ->
Tests = [
<<"/home/cowboy/../../../../../../file">>,
<<"/home/cowboy/../../etc/passwd">>
],
[{P, fun() ->
error = case fullpath(P) of
<< "/home/cowboy/", _/binary >> -> ok;
_ -> error
end
end} || P <- Tests].
good_path_win32_check_test_() ->
Tests = case os:type() of
{unix, _} ->
[];
{win32, _} ->
[
<<"c:/home/cowboy/file">>,
<<"c:/home/cowboy/file/">>,
<<"c:/home/cowboy/./file">>,
<<"c:/home/cowboy/././././././file">>,
<<"c:/home/cowboy/abc/../file">>,
<<"c:/home/cowboy/abc/../file">>,
<<"c:/home/cowboy/abc/./.././file">>
]
end,
[{P, fun() ->
case fullpath(P) of
<< "c:/home/cowboy/", _/binary >> -> ok
end
end} || P <- Tests].
bad_path_win32_check_test_() ->
Tests = case os:type() of
{unix, _} ->
[];
{win32, _} ->
[
<<"c:/home/cowboy/../../secretfile.bat">>,
<<"c:/home/cowboy/c:/secretfile.bat">>,
<<"c:/home/cowboy/..\\..\\secretfile.bat">>,
<<"c:/home/cowboy/c:\\secretfile.bat">>
]
end,
[{P, fun() ->
error = case fullpath(P) of
<< "c:/home/cowboy/", _/binary >> -> ok;
_ -> error
end
end} || P <- Tests].
-endif.
%% Reject requests that tried to access a file outside
%% the target directory.
-spec malformed_request(Req, State)
-> {boolean(), Req, State}.
malformed_request(Req, State) ->
{State =:= error, Req, State}.
%% Directories, files that can't be accessed at all and
%% files with no read flag are forbidden.
-spec forbidden(Req, State)
-> {boolean(), Req, State}
when State::state().
forbidden(Req, State={_, {ok, #file_info{type=directory}}, _}) ->
{true, Req, State};
forbidden(Req, State={_, {error, eacces}, _}) ->
{true, Req, State};
forbidden(Req, State={_, {ok, #file_info{access=Access}}, _})
when Access =:= write; Access =:= none ->
{true, Req, State};
forbidden(Req, State) ->
{false, Req, State}.
%% Detect the mimetype of the file.
-spec content_types_provided(Req, State)
-> {[{binary(), get_file}], Req, State}
when State::state().
content_types_provided(Req, State={Path, _, Extra}) ->
case lists:keyfind(mimetypes, 1, Extra) of
false ->
{[{cow_mimetypes:web(Path), get_file}], Req, State};
{mimetypes, Module, Function} ->
{[{Module:Function(Path), get_file}], Req, State};
{mimetypes, Type} ->
{[{Type, get_file}], Req, State}
end.
%% Assume the resource doesn't exist if it's not a regular file.
-spec resource_exists(Req, State)
-> {boolean(), Req, State}
when State::state().
resource_exists(Req, State={_, {ok, #file_info{type=regular}}, _}) ->
{true, Req, State};
resource_exists(Req, State) ->
{false, Req, State}.
%% Generate an etag for the file.
-spec generate_etag(Req, State)
-> {{strong | weak, binary()}, Req, State}
when State::state().
generate_etag(Req, State={Path, {ok, #file_info{size=Size, mtime=Mtime}},
Extra}) ->
case lists:keyfind(etag, 1, Extra) of
false ->
{generate_default_etag(Size, Mtime), Req, State};
{etag, Module, Function} ->
{Module:Function(Path, Size, Mtime), Req, State};
{etag, false} ->
{undefined, Req, State}
end.
generate_default_etag(Size, Mtime) ->
{strong, integer_to_binary(erlang:phash2({Size, Mtime}, 16#ffffffff))}.
%% Return the time of last modification of the file.
-spec last_modified(Req, State)
-> {calendar:datetime(), Req, State}
when State::state().
last_modified(Req, State={_, {ok, #file_info{mtime=Modified}}, _}) ->
{Modified, Req, State}.
%% Stream the file.
%% @todo Export cowboy_req:resp_body_fun()?
-spec get_file(Req, State)
-> {{stream, non_neg_integer(), fun()}, Req, State}
when State::state().
get_file(Req, State={Path, {ok, #file_info{size=Size}}, _}) ->
Sendfile = fun (Socket, Transport) ->
case Transport:sendfile(Socket, Path) of
{ok, _} -> ok;
{error, closed} -> ok;
{error, etimedout} -> ok
end
end,
{{stream, Size, Sendfile}, Req, State}.
|
> > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
%% Copyright (c) 2013, James Fish <james@fishcakez.com>
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_sub_protocol).
-callback upgrade(Req, Env, module(), any())
-> {ok, Req, Env}
| {suspend, module(), atom(), [any()]}
| {halt, Req}
| {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec init([])
-> {ok, {{supervisor:strategy(), 10, 10}, [supervisor:child_spec()]}}.
init([]) ->
Procs = [{cowboy_clock, {cowboy_clock, start_link, []},
permanent, 5000, worker, [cowboy_clock]}],
{ok, {{one_for_one, 10, 10}, Procs}}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% Cowboy supports versions 7 through 17 of the Websocket drafts.
%% It also supports RFC6455, the proposed standard for Websocket.
-module(cowboy_websocket).
-behaviour(cowboy_sub_protocol).
-export([upgrade/4]).
-export([handler_loop/4]).
-type close_code() :: 1000..4999.
-export_type([close_code/0]).
-type frame() :: close | ping | pong
| {text | binary | close | ping | pong, iodata()}
| {close, close_code(), iodata()}.
-export_type([frame/0]).
-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.
-type mask_key() :: 0..16#ffffffff.
-type frag_state() :: undefined
| {nofin, opcode(), binary()} | {fin, opcode(), binary()}.
-type rsv() :: << _:3 >>.
-type terminate_reason() :: {normal | error | remote, atom()}
| {remote, close_code(), binary()}.
-record(state, {
env :: cowboy_middleware:env(),
socket = undefined :: inet:socket(),
transport = undefined :: module(),
handler :: module(),
key = undefined :: undefined | binary(),
timeout = infinity :: timeout(),
timeout_ref = undefined :: undefined | reference(),
messages = undefined :: undefined | {atom(), atom(), atom()},
hibernate = false :: boolean(),
frag_state = undefined :: frag_state(),
utf8_state = <<>> :: binary(),
deflate_frame = false :: boolean(),
inflate_state :: undefined | port(),
deflate_state :: undefined | port()
}).
-spec upgrade(Req, Env, module(), any())
-> {ok, Req, Env}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade(Req, Env, Handler, HandlerOpts) ->
{_, Ref} = lists:keyfind(listener, 1, Env),
ranch:remove_connection(Ref),
[Socket, Transport] = cowboy_req:get([socket, transport], Req),
State = #state{env=Env, socket=Socket, transport=Transport,
handler=Handler},
try websocket_upgrade(State, Req) of
{ok, State2, Req2} ->
handler_init(State2, Req2, HandlerOpts)
catch _:_ ->
receive
{cowboy_req, resp_sent} -> ok
after 0 ->
_ = cowboy_req:reply(400, Req),
exit(normal)
end
end.
-spec websocket_upgrade(#state{}, Req)
-> {ok, #state{}, Req} when Req::cowboy_req:req().
websocket_upgrade(State, Req) ->
{ok, ConnTokens, Req2}
= cowboy_req:parse_header(<<"connection">>, Req),
true = lists:member(<<"upgrade">>, ConnTokens),
%% @todo Should probably send a 426 if the Upgrade header is missing.
{ok, [<<"websocket">>], Req3}
= cowboy_req:parse_header(<<"upgrade">>, Req2),
{Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3),
IntVersion = list_to_integer(binary_to_list(Version)),
true = (IntVersion =:= 7) orelse (IntVersion =:= 8)
orelse (IntVersion =:= 13),
{Key, Req5} = cowboy_req:header(<<"sec-websocket-key">>, Req4),
false = Key =:= undefined,
websocket_extensions(State#state{key=Key},
cowboy_req:set_meta(websocket_version, IntVersion, Req5)).
-spec websocket_extensions(#state{}, Req)
-> {ok, #state{}, Req} when Req::cowboy_req:req().
websocket_extensions(State, Req) ->
case cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req) of
{ok, Extensions, Req2} when Extensions =/= undefined ->
[Compress] = cowboy_req:get([resp_compress], Req),
case lists:keyfind(<<"x-webkit-deflate-frame">>, 1, Extensions) of
{<<"x-webkit-deflate-frame">>, []} when Compress =:= true ->
Inflate = zlib:open(),
Deflate = zlib:open(),
% Since we are negotiating an unconstrained deflate-frame
% then we must be willing to accept frames using the
% maximum window size which is 2^15. The negative value
% indicates that zlib headers are not used.
ok = zlib:inflateInit(Inflate, -15),
% Initialize the deflater with a window size of 2^15 bits and disable
% the zlib headers.
ok = zlib:deflateInit(Deflate, best_compression, deflated, -15, 8, default),
{ok, State#state{
deflate_frame = true,
inflate_state = Inflate,
deflate_state = Deflate
}, cowboy_req:set_meta(websocket_compress, true, Req2)};
_ ->
{ok, State, cowboy_req:set_meta(websocket_compress, false, Req2)}
end;
_ ->
{ok, State, cowboy_req:set_meta(websocket_compress, false, Req)}
end.
-spec handler_init(#state{}, Req, any())
-> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_init(State=#state{env=Env, transport=Transport,
handler=Handler}, Req, HandlerOpts) ->
try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of
{ok, Req2, HandlerState} ->
websocket_handshake(State, Req2, HandlerState);
{ok, Req2, HandlerState, hibernate} ->
websocket_handshake(State#state{hibernate=true},
Req2, HandlerState);
{ok, Req2, HandlerState, Timeout} ->
websocket_handshake(State#state{timeout=Timeout},
Req2, HandlerState);
{ok, Req2, HandlerState, Timeout, hibernate} ->
websocket_handshake(State#state{timeout=Timeout,
hibernate=true}, Req2, HandlerState);
{shutdown, Req2} ->
cowboy_req:ensure_response(Req2, 400),
{ok, Req2, [{result, closed}|Env]}
catch Class:Reason ->
Stacktrace = erlang:get_stacktrace(),
cowboy_req:maybe_reply(Stacktrace, Req),
erlang:Class([
{reason, Reason},
{mfa, {Handler, websocket_init, 3}},
{stacktrace, Stacktrace},
{req, cowboy_req:to_list(Req)},
{opts, HandlerOpts}
])
end.
-spec websocket_handshake(#state{}, Req, any())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
websocket_handshake(State=#state{
transport=Transport, key=Key, deflate_frame=DeflateFrame},
Req, HandlerState) ->
Challenge = base64:encode(crypto:hash(sha,
<< Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
Extensions = case DeflateFrame of
false -> [];
true -> [{<<"sec-websocket-extensions">>, <<"x-webkit-deflate-frame">>}]
end,
{ok, Req2} = cowboy_req:upgrade_reply(
101,
[{<<"upgrade">>, <<"websocket">>},
{<<"sec-websocket-accept">>, Challenge}|
Extensions],
Req),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
State2 = handler_loop_timeout(State),
handler_before_loop(State2#state{key=undefined,
messages=Transport:messages()}, Req2, HandlerState, <<>>).
-spec handler_before_loop(#state{}, Req, any(), binary())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_before_loop(State=#state{
socket=Socket, transport=Transport, hibernate=true},
Req, HandlerState, SoFar) ->
Transport:setopts(Socket, [{active, once}]),
{suspend, ?MODULE, handler_loop,
[State#state{hibernate=false}, Req, HandlerState, SoFar]};
handler_before_loop(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, SoFar) ->
Transport:setopts(Socket, [{active, once}]),
handler_loop(State, Req, HandlerState, SoFar).
-spec handler_loop_timeout(#state{}) -> #state{}.
handler_loop_timeout(State=#state{timeout=infinity}) ->
State#state{timeout_ref=undefined};
handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) ->
_ = case PrevRef of undefined -> ignore; PrevRef ->
erlang:cancel_timer(PrevRef) end,
TRef = erlang:start_timer(Timeout, self(), ?MODULE),
State#state{timeout_ref=TRef}.
-spec handler_loop(#state{}, Req, any(), binary())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_loop(State=#state{socket=Socket, messages={OK, Closed, Error},
timeout_ref=TRef}, Req, HandlerState, SoFar) ->
receive
{OK, Socket, Data} ->
State2 = handler_loop_timeout(State),
websocket_data(State2, Req, HandlerState,
<< SoFar/binary, Data/binary >>);
{Closed, Socket} ->
handler_terminate(State, Req, HandlerState, {error, closed});
{Error, Socket, Reason} ->
handler_terminate(State, Req, HandlerState, {error, Reason});
{timeout, TRef, ?MODULE} ->
websocket_close(State, Req, HandlerState, {normal, timeout});
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
handler_loop(State, Req, HandlerState, SoFar);
Message ->
handler_call(State, Req, HandlerState,
SoFar, websocket_info, Message, fun handler_before_loop/4)
end.
%% All frames passing through this function are considered valid,
%% with the only exception of text and close frames with a payload
%% which may still contain errors.
-spec websocket_data(#state{}, Req, any(), binary())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
%% RSV bits MUST be 0 unless an extension is negotiated
%% that defines meanings for non-zero values.
websocket_data(State, Req, HandlerState, << _:1, Rsv:3, _/bits >>)
when Rsv =/= 0, State#state.deflate_frame =:= false ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% Invalid opcode. Note that these opcodes may be used by extensions.
websocket_data(State, Req, HandlerState, << _:4, Opcode:4, _/bits >>)
when Opcode > 2, Opcode =/= 8, Opcode =/= 9, Opcode =/= 10 ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% Control frames MUST NOT be fragmented.
websocket_data(State, Req, HandlerState, << 0:1, _:3, Opcode:4, _/bits >>)
when Opcode >= 8 ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% A frame MUST NOT use the zero opcode unless fragmentation was initiated.
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
<< _:4, 0:4, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% Non-control opcode when expecting control message or next fragment.
websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState,
<< _:4, Opcode:4, _/bits >>)
when Opcode =/= 0, Opcode < 8 ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% Close control frame length MUST be 0 or >= 2.
websocket_data(State, Req, HandlerState, << _:4, 8:4, _:1, 1:7, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% Close control frame with incomplete close code. Need more data.
websocket_data(State, Req, HandlerState,
Data = << _:4, 8:4, 1:1, Len:7, _/bits >>)
when Len > 1, byte_size(Data) < 8 ->
handler_before_loop(State, Req, HandlerState, Data);
%% 7 bits payload length.
websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1,
Len:7, MaskKey:32, Rest/bits >>)
when Len < 126 ->
websocket_data(State, Req, HandlerState,
Opcode, Len, MaskKey, Rest, Rsv, Fin);
%% 16 bits payload length.
websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1,
126:7, Len:16, MaskKey:32, Rest/bits >>)
when Len > 125, Opcode < 8 ->
websocket_data(State, Req, HandlerState,
Opcode, Len, MaskKey, Rest, Rsv, Fin);
%% 63 bits payload length.
websocket_data(State, Req, HandlerState, << Fin:1, Rsv:3/bits, Opcode:4, 1:1,
127:7, 0:1, Len:63, MaskKey:32, Rest/bits >>)
when Len > 16#ffff, Opcode < 8 ->
websocket_data(State, Req, HandlerState,
Opcode, Len, MaskKey, Rest, Rsv, Fin);
%% When payload length is over 63 bits, the most significant bit MUST be 0.
websocket_data(State, Req, HandlerState, << _:8, 1:1, 127:7, 1:1, _:7, _/binary >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% All frames sent from the client to the server are masked.
websocket_data(State, Req, HandlerState, << _:8, 0:1, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% For the next two clauses, it can be one of the following:
%%
%% * The minimal number of bytes MUST be used to encode the length
%% * All control frames MUST have a payload length of 125 bytes or less
websocket_data(State, Req, HandlerState, << _:9, 126:7, _:48, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
websocket_data(State, Req, HandlerState, << _:9, 127:7, _:96, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
%% Need more data.
websocket_data(State, Req, HandlerState, Data) ->
handler_before_loop(State, Req, HandlerState, Data).
%% Initialize or update fragmentation state.
-spec websocket_data(#state{}, Req, any(),
opcode(), non_neg_integer(), mask_key(), binary(), rsv(), 0 | 1)
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
%% The opcode is only included in the first frame fragment.
websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
Opcode, Len, MaskKey, Data, Rsv, 0) ->
websocket_payload(State#state{frag_state={nofin, Opcode, <<>>}},
Req, HandlerState, 0, Len, MaskKey, <<>>, 0, Data, Rsv);
%% Subsequent frame fragments.
websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState,
0, Len, MaskKey, Data, Rsv, 0) ->
websocket_payload(State, Req, HandlerState,
0, Len, MaskKey, <<>>, 0, Data, Rsv);
%% Final frame fragment.
websocket_data(State=#state{frag_state={nofin, Opcode, SoFar}},
Req, HandlerState, 0, Len, MaskKey, Data, Rsv, 1) ->
websocket_payload(State#state{frag_state={fin, Opcode, SoFar}},
Req, HandlerState, 0, Len, MaskKey, <<>>, 0, Data, Rsv);
%% Unfragmented frame.
websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Data, Rsv, 1) ->
websocket_payload(State, Req, HandlerState,
Opcode, Len, MaskKey, <<>>, 0, Data, Rsv).
-spec websocket_payload(#state{}, Req, any(),
opcode(), non_neg_integer(), mask_key(), binary(), non_neg_integer(),
binary(), rsv())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
%% Close control frames with a payload MUST contain a valid close code.
websocket_payload(State, Req, HandlerState,
Opcode=8, Len, MaskKey, <<>>, 0,
<< MaskedCode:2/binary, Rest/bits >>, Rsv) ->
Unmasked = << Code:16 >> = websocket_unmask(MaskedCode, MaskKey, <<>>),
if Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006;
(Code > 1011) and (Code < 3000); Code > 4999 ->
websocket_close(State, Req, HandlerState, {error, badframe});
true ->
websocket_payload(State, Req, HandlerState,
Opcode, Len - 2, MaskKey, Unmasked, byte_size(MaskedCode),
Rest, Rsv)
end;
%% Text frames and close control frames MUST have a payload that is valid UTF-8.
websocket_payload(State=#state{utf8_state=Incomplete},
Req, HandlerState, Opcode, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv)
when (byte_size(Data) < Len) andalso ((Opcode =:= 1) orelse
((Opcode =:= 8) andalso (Unmasked =/= <<>>))) ->
Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
false ->
websocket_close(State2, Req, HandlerState, {error, badencoding});
Utf8State ->
websocket_payload_loop(State2#state{utf8_state=Utf8State},
Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
<< Unmasked/binary, Unmasked3/binary >>,
UnmaskedLen + byte_size(Data), Rsv)
end;
websocket_payload(State=#state{utf8_state=Incomplete},
Req, HandlerState, Opcode, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv)
when Opcode =:= 1; (Opcode =:= 8) and (Unmasked =/= <<>>) ->
<< End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
<<>> ->
websocket_dispatch(State2#state{utf8_state= <<>>},
Req, HandlerState, Rest, Opcode,
<< Unmasked/binary, Unmasked3/binary >>);
_ ->
websocket_close(State2, Req, HandlerState, {error, badencoding})
end;
%% Fragmented text frames may cut payload in the middle of UTF-8 codepoints.
websocket_payload(State=#state{frag_state={_, 1, _}, utf8_state=Incomplete},
Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv)
when byte_size(Data) < Len ->
Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
false ->
websocket_close(State2, Req, HandlerState, {error, badencoding});
Utf8State ->
websocket_payload_loop(State2#state{utf8_state=Utf8State},
Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
<< Unmasked/binary, Unmasked3/binary >>,
UnmaskedLen + byte_size(Data), Rsv)
end;
websocket_payload(State=#state{frag_state={Fin, 1, _}, utf8_state=Incomplete},
Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, UnmaskedLen,
Data, Rsv) ->
<< End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, Fin =:= fin, State),
case is_utf8(<< Incomplete/binary, Unmasked3/binary >>) of
<<>> ->
websocket_dispatch(State2#state{utf8_state= <<>>},
Req, HandlerState, Rest, Opcode,
<< Unmasked/binary, Unmasked3/binary >>);
Utf8State when is_binary(Utf8State), Fin =:= nofin ->
websocket_dispatch(State2#state{utf8_state=Utf8State},
Req, HandlerState, Rest, Opcode,
<< Unmasked/binary, Unmasked3/binary >>);
_ ->
websocket_close(State, Req, HandlerState, {error, badencoding})
end;
%% Other frames have a binary payload.
websocket_payload(State, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Data, Rsv)
when byte_size(Data) < Len ->
Unmasked2 = websocket_unmask(Data,
rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, false, State),
websocket_payload_loop(State2, Req, HandlerState,
Opcode, Len - byte_size(Data), MaskKey,
<< Unmasked/binary, Unmasked3/binary >>, UnmaskedLen + byte_size(Data),
Rsv);
websocket_payload(State, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Data, Rsv) ->
<< End:Len/binary, Rest/bits >> = Data,
Unmasked2 = websocket_unmask(End,
rotate_mask_key(MaskKey, UnmaskedLen), <<>>),
{Unmasked3, State2} = websocket_inflate_frame(Unmasked2, Rsv, true, State),
websocket_dispatch(State2, Req, HandlerState, Rest, Opcode,
<< Unmasked/binary, Unmasked3/binary >>).
-spec websocket_inflate_frame(binary(), rsv(), boolean(), #state{}) ->
{binary(), #state{}}.
websocket_inflate_frame(Data, << Rsv1:1, _:2 >>, _,
#state{deflate_frame = DeflateFrame} = State)
when DeflateFrame =:= false orelse Rsv1 =:= 0 ->
{Data, State};
websocket_inflate_frame(Data, << 1:1, _:2 >>, false, State) ->
Result = zlib:inflate(State#state.inflate_state, Data),
{iolist_to_binary(Result), State};
websocket_inflate_frame(Data, << 1:1, _:2 >>, true, State) ->
Result = zlib:inflate(State#state.inflate_state,
<< Data/binary, 0:8, 0:8, 255:8, 255:8 >>),
{iolist_to_binary(Result), State}.
-spec websocket_unmask(B, mask_key(), B) -> B when B::binary().
websocket_unmask(<<>>, _, Unmasked) ->
Unmasked;
websocket_unmask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
T = O bxor MaskKey,
websocket_unmask(Rest, MaskKey, << Acc/binary, T:32 >>);
websocket_unmask(<< O:24 >>, MaskKey, Acc) ->
<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:24 >>;
websocket_unmask(<< O:16 >>, MaskKey, Acc) ->
<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:16 >>;
websocket_unmask(<< O:8 >>, MaskKey, Acc) ->
<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:8 >>.
%% Because we unmask on the fly we need to continue from the right mask byte.
-spec rotate_mask_key(mask_key(), non_neg_integer()) -> mask_key().
rotate_mask_key(MaskKey, UnmaskedLen) ->
Left = UnmaskedLen rem 4,
Right = 4 - Left,
(MaskKey bsl (Left * 8)) + (MaskKey bsr (Right * 8)).
%% Returns <<>> if the argument is valid UTF-8, false if not,
%% or the incomplete part of the argument if we need more data.
-spec is_utf8(binary()) -> false | binary().
is_utf8(Valid = <<>>) ->
Valid;
is_utf8(<< _/utf8, Rest/binary >>) ->
is_utf8(Rest);
%% 2 bytes. Codepages C0 and C1 are invalid; fail early.
is_utf8(<< 2#1100000:7, _/bits >>) ->
false;
is_utf8(Incomplete = << 2#110:3, _:5 >>) ->
Incomplete;
%% 3 bytes.
is_utf8(Incomplete = << 2#1110:4, _:4 >>) ->
Incomplete;
is_utf8(Incomplete = << 2#1110:4, _:4, 2#10:2, _:6 >>) ->
Incomplete;
%% 4 bytes. Codepage F4 may have invalid values greater than 0x10FFFF.
is_utf8(<< 2#11110100:8, 2#10:2, High:6, _/bits >>) when High >= 2#10000 ->
false;
is_utf8(Incomplete = << 2#11110:5, _:3 >>) ->
Incomplete;
is_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6 >>) ->
Incomplete;
is_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6, 2#10:2, _:6 >>) ->
Incomplete;
%% Invalid.
is_utf8(_) ->
false.
-spec websocket_payload_loop(#state{}, Req, any(),
opcode(), non_neg_integer(), mask_key(), binary(),
non_neg_integer(), rsv())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
websocket_payload_loop(State=#state{socket=Socket, transport=Transport,
messages={OK, Closed, Error}, timeout_ref=TRef},
Req, HandlerState, Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Rsv) ->
Transport:setopts(Socket, [{active, once}]),
receive
{OK, Socket, Data} ->
State2 = handler_loop_timeout(State),
websocket_payload(State2, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Data, Rsv);
{Closed, Socket} ->
handler_terminate(State, Req, HandlerState, {error, closed});
{Error, Socket, Reason} ->
handler_terminate(State, Req, HandlerState, {error, Reason});
{timeout, TRef, ?MODULE} ->
websocket_close(State, Req, HandlerState, {normal, timeout});
{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
websocket_payload_loop(State, Req, HandlerState,
Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Rsv);
Message ->
handler_call(State, Req, HandlerState,
<<>>, websocket_info, Message,
fun (State2, Req2, HandlerState2, _) ->
websocket_payload_loop(State2, Req2, HandlerState2,
Opcode, Len, MaskKey, Unmasked, UnmaskedLen, Rsv)
end)
end.
-spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
%% Continuation frame.
websocket_dispatch(State=#state{frag_state={nofin, Opcode, SoFar}},
Req, HandlerState, RemainingData, 0, Payload) ->
websocket_data(State#state{frag_state={nofin, Opcode,
<< SoFar/binary, Payload/binary >>}}, Req, HandlerState, RemainingData);
%% Last continuation frame.
websocket_dispatch(State=#state{frag_state={fin, Opcode, SoFar}},
Req, HandlerState, RemainingData, 0, Payload) ->
websocket_dispatch(State#state{frag_state=undefined}, Req, HandlerState,
RemainingData, Opcode, << SoFar/binary, Payload/binary >>);
%% Text frame.
websocket_dispatch(State, Req, HandlerState, RemainingData, 1, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {text, Payload}, fun websocket_data/4);
%% Binary frame.
websocket_dispatch(State, Req, HandlerState, RemainingData, 2, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {binary, Payload}, fun websocket_data/4);
%% Close control frame.
websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, <<>>) ->
websocket_close(State, Req, HandlerState, {remote, closed});
websocket_dispatch(State, Req, HandlerState, _RemainingData, 8,
<< Code:16, Payload/bits >>) ->
websocket_close(State, Req, HandlerState, {remote, Code, Payload});
%% Ping control frame. Send a pong back and forward the ping to the handler.
websocket_dispatch(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, RemainingData, 9, Payload) ->
Len = payload_length_to_binary(iolist_size(Payload)),
Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>),
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {ping, Payload}, fun websocket_data/4);
%% Pong control frame.
websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {pong, Payload}, fun websocket_data/4).
-spec handler_call(#state{}, Req, any(), binary(), atom(), any(), fun())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
handler_call(State=#state{handler=Handler}, Req, HandlerState,
RemainingData, Callback, Message, NextState) ->
try Handler:Callback(Message, Req, HandlerState) of
{ok, Req2, HandlerState2} ->
NextState(State, Req2, HandlerState2, RemainingData);
{ok, Req2, HandlerState2, hibernate} ->
NextState(State#state{hibernate=true},
Req2, HandlerState2, RemainingData);
{reply, Payload, Req2, HandlerState2}
when is_list(Payload) ->
case websocket_send_many(Payload, State) of
{ok, State2} ->
NextState(State2, Req2, HandlerState2, RemainingData);
{shutdown, State2} ->
handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
{{error, _} = Error, State2} ->
handler_terminate(State2, Req2, HandlerState2, Error)
end;
{reply, Payload, Req2, HandlerState2, hibernate}
when is_list(Payload) ->
case websocket_send_many(Payload, State) of
{ok, State2} ->
NextState(State2#state{hibernate=true},
Req2, HandlerState2, RemainingData);
{shutdown, State2} ->
handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
{{error, _} = Error, State2} ->
handler_terminate(State2, Req2, HandlerState2, Error)
end;
{reply, Payload, Req2, HandlerState2} ->
case websocket_send(Payload, State) of
{ok, State2} ->
NextState(State2, Req2, HandlerState2, RemainingData);
{shutdown, State2} ->
handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
{{error, _} = Error, State2} ->
handler_terminate(State2, Req2, HandlerState2, Error)
end;
{reply, Payload, Req2, HandlerState2, hibernate} ->
case websocket_send(Payload, State) of
{ok, State2} ->
NextState(State2#state{hibernate=true},
Req2, HandlerState2, RemainingData);
{shutdown, State2} ->
handler_terminate(State2, Req2, HandlerState2,
{normal, shutdown});
{{error, _} = Error, State2} ->
handler_terminate(State2, Req2, HandlerState2, Error)
end;
{shutdown, Req2, HandlerState2} ->
websocket_close(State, Req2, HandlerState2, {normal, shutdown})
catch Class:Reason ->
_ = websocket_close(State, Req, HandlerState, {error, handler}),
erlang:Class([
{reason, Reason},
{mfa, {Handler, Callback, 3}},
{stacktrace, erlang:get_stacktrace()},
{msg, Message},
{req, cowboy_req:to_list(Req)},
{state, HandlerState}
])
end.
websocket_opcode(text) -> 1;
websocket_opcode(binary) -> 2;
websocket_opcode(close) -> 8;
websocket_opcode(ping) -> 9;
websocket_opcode(pong) -> 10.
-spec websocket_deflate_frame(opcode(), binary(), #state{}) ->
{binary(), rsv(), #state{}}.
websocket_deflate_frame(Opcode, Payload,
State=#state{deflate_frame = DeflateFrame})
when DeflateFrame =:= false orelse Opcode >= 8 ->
{Payload, << 0:3 >>, State};
websocket_deflate_frame(_, Payload, State=#state{deflate_state = Deflate}) ->
Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)),
DeflatedBodyLength = erlang:size(Deflated) - 4,
Deflated1 = case Deflated of
<< Body:DeflatedBodyLength/binary, 0:8, 0:8, 255:8, 255:8 >> -> Body;
_ -> Deflated
end,
{Deflated1, << 1:1, 0:2 >>, State}.
-spec websocket_send(frame(), #state{})
-> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}.
websocket_send(Type, State=#state{socket=Socket, transport=Transport})
when Type =:= close ->
Opcode = websocket_opcode(Type),
case Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>) of
ok -> {shutdown, State};
Error -> {Error, State}
end;
websocket_send(Type, State=#state{socket=Socket, transport=Transport})
when Type =:= ping; Type =:= pong ->
Opcode = websocket_opcode(Type),
{Transport:send(Socket, << 1:1, 0:3, Opcode:4, 0:8 >>), State};
websocket_send({close, Payload}, State) ->
websocket_send({close, 1000, Payload}, State);
websocket_send({Type = close, StatusCode, Payload}, State=#state{
socket=Socket, transport=Transport}) ->
Opcode = websocket_opcode(Type),
Len = 2 + iolist_size(Payload),
%% Control packets must not be > 125 in length.
true = Len =< 125,
BinLen = payload_length_to_binary(Len),
Transport:send(Socket,
[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits, StatusCode:16 >>, Payload]),
{shutdown, State};
websocket_send({Type, Payload0}, State=#state{socket=Socket, transport=Transport}) ->
Opcode = websocket_opcode(Type),
{Payload, Rsv, State2} = websocket_deflate_frame(Opcode, iolist_to_binary(Payload0), State),
Len = iolist_size(Payload),
%% Control packets must not be > 125 in length.
true = if Type =:= ping; Type =:= pong ->
Len =< 125;
true ->
true
end,
BinLen = payload_length_to_binary(Len),
{Transport:send(Socket,
[<< 1:1, Rsv/bits, Opcode:4, 0:1, BinLen/bits >>, Payload]), State2}.
-spec websocket_send_many([frame()], #state{})
-> {ok, #state{}} | {shutdown, #state{}} | {{error, atom()}, #state{}}.
websocket_send_many([], State) ->
{ok, State};
websocket_send_many([Frame|Tail], State) ->
case websocket_send(Frame, State) of
{ok, State2} -> websocket_send_many(Tail, State2);
{shutdown, State2} -> {shutdown, State2};
{Error, State2} -> {Error, State2}
end.
-spec websocket_close(#state{}, Req, any(), terminate_reason())
-> {ok, Req, cowboy_middleware:env()}
when Req::cowboy_req:req().
websocket_close(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, Reason) ->
case Reason of
{normal, _} ->
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>);
{error, badframe} ->
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1002:16 >>);
{error, badencoding} ->
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1007:16 >>);
{error, handler} ->
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1011:16 >>);
{remote, closed} ->
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>);
{remote, Code, _} ->
Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, Code:16 >>)
end,
handler_terminate(State, Req, HandlerState, Reason).
-spec handler_terminate(#state{}, Req, any(), terminate_reason())
-> {ok, Req, cowboy_middleware:env()}
when Req::cowboy_req:req().
handler_terminate(#state{env=Env, handler=Handler},
Req, HandlerState, TerminateReason) ->
try
Handler:websocket_terminate(TerminateReason, Req, HandlerState)
catch Class:Reason ->
erlang:Class([
{reason, Reason},
{mfa, {Handler, websocket_terminate, 3}},
{stacktrace, erlang:get_stacktrace()},
{req, cowboy_req:to_list(Req)},
{state, HandlerState},
{terminate_reason, TerminateReason}
])
end,
{ok, Req, [{result, closed}|Env]}.
-spec payload_length_to_binary(0..16#7fffffffffffffff)
-> << _:7 >> | << _:23 >> | << _:71 >>.
payload_length_to_binary(N) ->
case N of
N when N =< 125 -> << N:7 >>;
N when N =< 16#ffff -> << 126:7, N:16 >>;
N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>
end.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_websocket_handler).
-type opts() :: any().
-type state() :: any().
-type terminate_reason() :: {normal, shutdown}
| {normal, timeout}
| {error, closed}
| {remote, closed}
| {remote, cowboy_websocket:close_code(), binary()}
| {error, badencoding}
| {error, badframe}
| {error, atom()}.
-callback websocket_init(atom(), Req, opts())
-> {ok, Req, state()}
| {ok, Req, state(), hibernate}
| {ok, Req, state(), timeout()}
| {ok, Req, state(), timeout(), hibernate}
| {shutdown, Req}
when Req::cowboy_req:req().
-callback websocket_handle({text | binary | ping | pong, binary()}, Req, State)
-> {ok, Req, State}
| {ok, Req, State, hibernate}
| {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State}
| {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State, hibernate}
| {shutdown, Req, State}
when Req::cowboy_req:req(), State::state().
-callback websocket_info(any(), Req, State)
-> {ok, Req, State}
| {ok, Req, State, hibernate}
| {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State}
| {reply, cowboy_websocket:frame() | [cowboy_websocket:frame()], Req, State, hibernate}
| {shutdown, Req, State}
when Req::cowboy_req:req(), State::state().
-callback websocket_terminate(terminate_reason(), cowboy_req:req(), state())
-> ok.
|
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# See LICENSE for licensing information.
PROJECT = cowlib
PLT_APPS = crypto
CI_OTP = OTP_R15B OTP_R15B01 OTP_R15B02 OTP_R15B03-1 OTP_R16B OTP_R16B01 OTP_R16B02 OTP_R16B03-1 OTP-17.0.2 OTP-17.1.2 OTP-17.2.2 OTP-17.3.4 OTP-17.4.1 OTP-17.5.6.3 OTP-18.0.3
include erlang.mk
TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}' -DEXTRA=1
.PHONY: gen perfs
# Mimetypes module generator.
GEN_URL = http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
GEN_SRC = src/cow_mimetypes.erl.src
GEN_OUT = src/cow_mimetypes.erl
gen:
$(gen_verbose) cat $(GEN_SRC) \
| head -n `grep -n "%% GENERATED" $(GEN_SRC) | cut -d : -f 1` \
> $(GEN_OUT)
$(gen_verbose) wget -qO - $(GEN_URL) \
| grep -v ^# \
| awk '{for (i=2; i<=NF; i++) if ($$i != "") { \
split($$1, a, "/"); \
print "all_ext(<<\"" $$i "\">>) -> {<<\"" \
a[1] "\">>, <<\"" a[2] "\">>, []};"}}' \
| sort \
| uniq -w 25 \
>> $(GEN_OUT)
$(gen_verbose) cat $(GEN_SRC) \
| tail -n +`grep -n "%% GENERATED" $(GEN_SRC) | cut -d : -f 1` \
>> $(GEN_OUT)
# Performance testing.
deps/horse:
git clone -n -- https://github.com/extend/horse $(DEPS_DIR)/horse
cd $(DEPS_DIR)/horse ; git checkout -q master
$(MAKE) -C $(DEPS_DIR)/horse
perfs: ERLC_OPTS += -DPERF=1 +'{parse_transform, horse_autoexport}' -DEXTRA=1
perfs: clean deps deps/horse app
$(gen_verbose) erl -noshell -pa ebin deps/horse/ebin \
-eval 'horse:app_perf($(PROJECT)), init:stop().'
|
> > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | Cowlib ====== Cowlib is a support library for manipulating Web protocols. Goals ----- Cowlib provides libraries for parsing and building messages for various Web protocols, including SPDY, HTTP and Websocket. It is optimized for completeness rather than speed. No value is ignored, they are all returned. Support ------- * Official IRC Channel: #ninenines on irc.freenode.net * [Mailing Lists](http://lists.ninenines.eu) * [Commercial Support](http://ninenines.eu/support) |
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > | 1 2 3 4 5 6 7 8 |
{application,cowlib,
[{description,"Support library for manipulating Web protocols."},
{vsn,"1.0.2"},
{id,"git"},
{modules,[cow_cookie,cow_date,cow_http,cow_http_hd,cow_http_te,
cow_mimetypes,cow_multipart,cow_qs,cow_spdy]},
{registered,[]},
{applications,[kernel,stdlib,crypto]}]}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 |
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.PHONY: all app deps search rel docs install-docs check tests clean distclean help erlang-mk
ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST)))
ERLANG_MK_VERSION = 1.2.0-646-gcf43676
# Core configuration.
PROJECT ?= $(notdir $(CURDIR))
PROJECT := $(strip $(PROJECT))
PROJECT_VERSION ?= rolling
# Verbosity.
V ?= 0
verbose_0 = @
verbose = $(verbose_$(V))
gen_verbose_0 = @echo " GEN " $@;
gen_verbose = $(gen_verbose_$(V))
# Temporary files directory.
ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk
export ERLANG_MK_TMP
# "erl" command.
ERL = erl +A0 -noinput -boot start_clean
# Platform detection.
# @todo Add Windows/Cygwin detection eventually.
ifeq ($(PLATFORM),)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
PLATFORM = linux
else ifeq ($(UNAME_S),Darwin)
PLATFORM = darwin
else ifeq ($(UNAME_S),SunOS)
PLATFORM = solaris
else ifeq ($(UNAME_S),GNU)
PLATFORM = gnu
else ifeq ($(UNAME_S),FreeBSD)
PLATFORM = freebsd
else ifeq ($(UNAME_S),NetBSD)
PLATFORM = netbsd
else ifeq ($(UNAME_S),OpenBSD)
PLATFORM = openbsd
else
$(error Unable to detect platform. Please open a ticket with the output of uname -a.)
endif
export PLATFORM
endif
# Core targets.
ifneq ($(words $(MAKECMDGOALS)),1)
.NOTPARALLEL:
endif
all:: deps
$(verbose) $(MAKE) --no-print-directory app
$(verbose) $(MAKE) --no-print-directory rel
# Noop to avoid a Make warning when there's nothing to do.
rel::
$(verbose) echo -n
check:: clean app tests
clean:: clean-crashdump
clean-crashdump:
ifneq ($(wildcard erl_crash.dump),)
$(gen_verbose) rm -f erl_crash.dump
endif
distclean:: clean
help::
$(verbose) printf "%s\n" \
"erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \
"Copyright (c) 2013-2015 Loïc Hoguin <essen@ninenines.eu>" \
"" \
"Usage: [V=1] $(MAKE) [-jNUM] [target]..." \
"" \
"Core targets:" \
" all Run deps, app and rel targets in that order" \
" app Compile the project" \
" deps Fetch dependencies (if needed) and compile them" \
" search q=... Search for a package in the built-in index" \
" rel Build a release for this project, if applicable" \
" docs Build the documentation for this project" \
" install-docs Install the man pages for this project" \
" check Compile and run all tests and analysis for this project" \
" tests Run the tests for this project" \
" clean Delete temporary and output files from most targets" \
" distclean Delete all temporary and output files" \
" help Display this help and exit" \
" erlang-mk Update erlang.mk to the latest version"
# Core functions.
empty :=
space := $(empty) $(empty)
tab := $(empty) $(empty)
comma := ,
define newline
endef
define comma_list
$(subst $(space),$(comma),$(strip $(1)))
endef
# Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy.
define erlang
$(ERL) $(2) -pz $(ERLANG_MK_TMP)/rebar/ebin -eval "$(subst $(newline),,$(subst ",\",$(1)))" -- erlang.mk
endef
ifeq ($(shell which wget 2>/dev/null | wc -l), 1)
define core_http_get
wget --no-check-certificate -O $(1) $(2)|| rm $(1)
endef
else
define core_http_get.erl
ssl:start(),
inets:start(),
case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of
{ok, {{_, 200, _}, _, Body}} ->
case file:write_file("$(1)", Body) of
ok -> ok;
{error, R1} -> halt(R1)
end;
{error, R2} ->
halt(R2)
end,
halt(0).
endef
define core_http_get
$(call erlang,$(call core_http_get.erl,$(1),$(2)))
endef
endif
core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
core_find = $(foreach d,$(call core_ls,$1*),$(call core_find,$d/,$2) $(filter $(subst *,%,$2),$d))
core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))
# @todo On Windows: $(shell dir /B $(1)); make sure to handle when no file exists.
core_ls = $(filter-out $(1),$(shell echo $(1)))
# Automated update.
ERLANG_MK_BUILD_CONFIG ?= build.config
ERLANG_MK_BUILD_DIR ?= .erlang.mk.build
erlang-mk:
git clone https://github.com/ninenines/erlang.mk $(ERLANG_MK_BUILD_DIR)
if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR); fi
cd $(ERLANG_MK_BUILD_DIR) && $(MAKE)
cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
rm -rf $(ERLANG_MK_BUILD_DIR)
# The erlang.mk package index is bundled in the default erlang.mk build.
# Search for the string "copyright" to skip to the rest of the code.
PACKAGES += aberth
pkg_aberth_name = aberth
pkg_aberth_description = Generic BERT-RPC server in Erlang
pkg_aberth_homepage = https://github.com/a13x/aberth
pkg_aberth_fetch = git
pkg_aberth_repo = https://github.com/a13x/aberth
pkg_aberth_commit = master
PACKAGES += active
pkg_active_name = active
pkg_active_description = Active development for Erlang: rebuild and reload source/binary files while the VM is running
pkg_active_homepage = https://github.com/proger/active
pkg_active_fetch = git
pkg_active_repo = https://github.com/proger/active
pkg_active_commit = master
PACKAGES += actordb_core
pkg_actordb_core_name = actordb_core
pkg_actordb_core_description = ActorDB main source
pkg_actordb_core_homepage = http://www.actordb.com/
pkg_actordb_core_fetch = git
pkg_actordb_core_repo = https://github.com/biokoda/actordb_core
pkg_actordb_core_commit = master
PACKAGES += actordb_thrift
pkg_actordb_thrift_name = actordb_thrift
pkg_actordb_thrift_description = Thrift API for ActorDB
pkg_actordb_thrift_homepage = http://www.actordb.com/
pkg_actordb_thrift_fetch = git
pkg_actordb_thrift_repo = https://github.com/biokoda/actordb_thrift
pkg_actordb_thrift_commit = master
PACKAGES += aleppo
pkg_aleppo_name = aleppo
pkg_aleppo_description = Alternative Erlang Pre-Processor
pkg_aleppo_homepage = https://github.com/ErlyORM/aleppo
pkg_aleppo_fetch = git
pkg_aleppo_repo = https://github.com/ErlyORM/aleppo
pkg_aleppo_commit = master
PACKAGES += alog
pkg_alog_name = alog
pkg_alog_description = Simply the best logging framework for Erlang
pkg_alog_homepage = https://github.com/siberian-fast-food/alogger
pkg_alog_fetch = git
pkg_alog_repo = https://github.com/siberian-fast-food/alogger
pkg_alog_commit = master
PACKAGES += amqp_client
pkg_amqp_client_name = amqp_client
pkg_amqp_client_description = RabbitMQ Erlang AMQP client
pkg_amqp_client_homepage = https://www.rabbitmq.com/erlang-client-user-guide.html
pkg_amqp_client_fetch = git
pkg_amqp_client_repo = https://github.com/rabbitmq/rabbitmq-erlang-client.git
pkg_amqp_client_commit = master
PACKAGES += annotations
pkg_annotations_name = annotations
pkg_annotations_description = Simple code instrumentation utilities
pkg_annotations_homepage = https://github.com/hyperthunk/annotations
pkg_annotations_fetch = git
pkg_annotations_repo = https://github.com/hyperthunk/annotations
pkg_annotations_commit = master
PACKAGES += antidote
pkg_antidote_name = antidote
pkg_antidote_description = Large-scale computation without synchronisation
pkg_antidote_homepage = https://syncfree.lip6.fr/
pkg_antidote_fetch = git
pkg_antidote_repo = https://github.com/SyncFree/antidote
pkg_antidote_commit = master
PACKAGES += apns
pkg_apns_name = apns
pkg_apns_description = Apple Push Notification Server for Erlang
pkg_apns_homepage = http://inaka.github.com/apns4erl
pkg_apns_fetch = git
pkg_apns_repo = https://github.com/inaka/apns4erl
pkg_apns_commit = 1.0.4
PACKAGES += azdht
pkg_azdht_name = azdht
pkg_azdht_description = Azureus Distributed Hash Table (DHT) in Erlang
pkg_azdht_homepage = https://github.com/arcusfelis/azdht
pkg_azdht_fetch = git
pkg_azdht_repo = https://github.com/arcusfelis/azdht
pkg_azdht_commit = master
PACKAGES += backoff
pkg_backoff_name = backoff
pkg_backoff_description = Simple exponential backoffs in Erlang
pkg_backoff_homepage = https://github.com/ferd/backoff
pkg_backoff_fetch = git
pkg_backoff_repo = https://github.com/ferd/backoff
pkg_backoff_commit = master
PACKAGES += barrel_tcp
pkg_barrel_tcp_name = barrel_tcp
pkg_barrel_tcp_description = barrel is a generic TCP acceptor pool with low latency in Erlang.
pkg_barrel_tcp_homepage = https://github.com/benoitc-attic/barrel_tcp
pkg_barrel_tcp_fetch = git
pkg_barrel_tcp_repo = https://github.com/benoitc-attic/barrel_tcp
pkg_barrel_tcp_commit = master
PACKAGES += basho_bench
pkg_basho_bench_name = basho_bench
pkg_basho_bench_description = A load-generation and testing tool for basically whatever you can write a returning Erlang function for.
pkg_basho_bench_homepage = https://github.com/basho/basho_bench
pkg_basho_bench_fetch = git
pkg_basho_bench_repo = https://github.com/basho/basho_bench
pkg_basho_bench_commit = master
PACKAGES += bcrypt
pkg_bcrypt_name = bcrypt
pkg_bcrypt_description = Bcrypt Erlang / C library
pkg_bcrypt_homepage = https://github.com/riverrun/branglecrypt
pkg_bcrypt_fetch = git
pkg_bcrypt_repo = https://github.com/riverrun/branglecrypt
pkg_bcrypt_commit = master
PACKAGES += beam
pkg_beam_name = beam
pkg_beam_description = BEAM emulator written in Erlang
pkg_beam_homepage = https://github.com/tonyrog/beam
pkg_beam_fetch = git
pkg_beam_repo = https://github.com/tonyrog/beam
pkg_beam_commit = master
PACKAGES += beanstalk
pkg_beanstalk_name = beanstalk
pkg_beanstalk_description = An Erlang client for beanstalkd
pkg_beanstalk_homepage = https://github.com/tim/erlang-beanstalk
pkg_beanstalk_fetch = git
pkg_beanstalk_repo = https://github.com/tim/erlang-beanstalk
pkg_beanstalk_commit = master
PACKAGES += bear
pkg_bear_name = bear
pkg_bear_description = a set of statistics functions for erlang
pkg_bear_homepage = https://github.com/boundary/bear
pkg_bear_fetch = git
pkg_bear_repo = https://github.com/boundary/bear
pkg_bear_commit = master
PACKAGES += bertconf
pkg_bertconf_name = bertconf
pkg_bertconf_description = Make ETS tables out of statc BERT files that are auto-reloaded
pkg_bertconf_homepage = https://github.com/ferd/bertconf
pkg_bertconf_fetch = git
pkg_bertconf_repo = https://github.com/ferd/bertconf
pkg_bertconf_commit = master
PACKAGES += bifrost
pkg_bifrost_name = bifrost
pkg_bifrost_description = Erlang FTP Server Framework
pkg_bifrost_homepage = https://github.com/thorstadt/bifrost
pkg_bifrost_fetch = git
pkg_bifrost_repo = https://github.com/thorstadt/bifrost
pkg_bifrost_commit = master
PACKAGES += binpp
pkg_binpp_name = binpp
pkg_binpp_description = Erlang Binary Pretty Printer
pkg_binpp_homepage = https://github.com/jtendo/binpp
pkg_binpp_fetch = git
pkg_binpp_repo = https://github.com/jtendo/binpp
pkg_binpp_commit = master
PACKAGES += bisect
pkg_bisect_name = bisect
pkg_bisect_description = Ordered fixed-size binary dictionary in Erlang
pkg_bisect_homepage = https://github.com/knutin/bisect
pkg_bisect_fetch = git
pkg_bisect_repo = https://github.com/knutin/bisect
pkg_bisect_commit = master
PACKAGES += bitcask
pkg_bitcask_name = bitcask
pkg_bitcask_description = because you need another a key/value storage engine
pkg_bitcask_homepage = https://github.com/basho/bitcask
pkg_bitcask_fetch = git
pkg_bitcask_repo = https://github.com/basho/bitcask
pkg_bitcask_commit = master
PACKAGES += bitstore
pkg_bitstore_name = bitstore
pkg_bitstore_description = A document based ontology development environment
pkg_bitstore_homepage = https://github.com/bdionne/bitstore
pkg_bitstore_fetch = git
pkg_bitstore_repo = https://github.com/bdionne/bitstore
pkg_bitstore_commit = master
PACKAGES += bootstrap
pkg_bootstrap_name = bootstrap
pkg_bootstrap_description = A simple, yet powerful Erlang cluster bootstrapping application.
pkg_bootstrap_homepage = https://github.com/schlagert/bootstrap
pkg_bootstrap_fetch = git
pkg_bootstrap_repo = https://github.com/schlagert/bootstrap
pkg_bootstrap_commit = master
PACKAGES += boss_db
pkg_boss_db_name = boss_db
pkg_boss_db_description = BossDB: a sharded, caching, pooling, evented ORM for Erlang
pkg_boss_db_homepage = https://github.com/ErlyORM/boss_db
pkg_boss_db_fetch = git
pkg_boss_db_repo = https://github.com/ErlyORM/boss_db
pkg_boss_db_commit = master
PACKAGES += boss
pkg_boss_name = boss
pkg_boss_description = Erlang web MVC, now featuring Comet
pkg_boss_homepage = https://github.com/ChicagoBoss/ChicagoBoss
pkg_boss_fetch = git
pkg_boss_repo = https://github.com/ChicagoBoss/ChicagoBoss
pkg_boss_commit = master
PACKAGES += bson
pkg_bson_name = bson
pkg_bson_description = BSON documents in Erlang, see bsonspec.org
pkg_bson_homepage = https://github.com/comtihon/bson-erlang
pkg_bson_fetch = git
pkg_bson_repo = https://github.com/comtihon/bson-erlang
pkg_bson_commit = master
PACKAGES += bullet
pkg_bullet_name = bullet
pkg_bullet_description = Simple, reliable, efficient streaming for Cowboy.
pkg_bullet_homepage = http://ninenines.eu
pkg_bullet_fetch = git
pkg_bullet_repo = https://github.com/extend/bullet
pkg_bullet_commit = master
PACKAGES += cache
pkg_cache_name = cache
pkg_cache_description = Erlang in-memory cache
pkg_cache_homepage = https://github.com/fogfish/cache
pkg_cache_fetch = git
pkg_cache_repo = https://github.com/fogfish/cache
pkg_cache_commit = master
PACKAGES += cake
pkg_cake_name = cake
pkg_cake_description = Really simple terminal colorization
pkg_cake_homepage = https://github.com/darach/cake-erl
pkg_cake_fetch = git
pkg_cake_repo = https://github.com/darach/cake-erl
pkg_cake_commit = v0.1.2
PACKAGES += carotene
pkg_carotene_name = carotene
pkg_carotene_description = Real-time server
pkg_carotene_homepage = https://github.com/carotene/carotene
pkg_carotene_fetch = git
pkg_carotene_repo = https://github.com/carotene/carotene
pkg_carotene_commit = master
PACKAGES += cberl
pkg_cberl_name = cberl
pkg_cberl_description = NIF based Erlang bindings for Couchbase
pkg_cberl_homepage = https://github.com/chitika/cberl
pkg_cberl_fetch = git
pkg_cberl_repo = https://github.com/chitika/cberl
pkg_cberl_commit = master
PACKAGES += cecho
pkg_cecho_name = cecho
pkg_cecho_description = An ncurses library for Erlang
pkg_cecho_homepage = https://github.com/mazenharake/cecho
pkg_cecho_fetch = git
pkg_cecho_repo = https://github.com/mazenharake/cecho
pkg_cecho_commit = master
PACKAGES += cferl
pkg_cferl_name = cferl
pkg_cferl_description = Rackspace / Open Stack Cloud Files Erlang Client
pkg_cferl_homepage = https://github.com/ddossot/cferl
pkg_cferl_fetch = git
pkg_cferl_repo = https://github.com/ddossot/cferl
pkg_cferl_commit = master
PACKAGES += chaos_monkey
pkg_chaos_monkey_name = chaos_monkey
pkg_chaos_monkey_description = This is The CHAOS MONKEY. It will kill your processes.
pkg_chaos_monkey_homepage = https://github.com/dLuna/chaos_monkey
pkg_chaos_monkey_fetch = git
pkg_chaos_monkey_repo = https://github.com/dLuna/chaos_monkey
pkg_chaos_monkey_commit = master
PACKAGES += check_node
pkg_check_node_name = check_node
pkg_check_node_description = Nagios Scripts for monitoring Riak
pkg_check_node_homepage = https://github.com/basho-labs/riak_nagios
pkg_check_node_fetch = git
pkg_check_node_repo = https://github.com/basho-labs/riak_nagios
pkg_check_node_commit = master
PACKAGES += chronos
pkg_chronos_name = chronos
pkg_chronos_description = Timer module for Erlang that makes it easy to abstact time out of the tests.
pkg_chronos_homepage = https://github.com/lehoff/chronos
pkg_chronos_fetch = git
pkg_chronos_repo = https://github.com/lehoff/chronos
pkg_chronos_commit = master
PACKAGES += classifier
pkg_classifier_name = classifier
pkg_classifier_description = An Erlang Bayesian Filter and Text Classifier
pkg_classifier_homepage = https://github.com/inaka/classifier
pkg_classifier_fetch = git
pkg_classifier_repo = https://github.com/inaka/classifier
pkg_classifier_commit = master
PACKAGES += clique
pkg_clique_name = clique
pkg_clique_description = CLI Framework for Erlang
pkg_clique_homepage = https://github.com/basho/clique
pkg_clique_fetch = git
pkg_clique_repo = https://github.com/basho/clique
pkg_clique_commit = develop
PACKAGES += cl
pkg_cl_name = cl
pkg_cl_description = OpenCL binding for Erlang
pkg_cl_homepage = https://github.com/tonyrog/cl
pkg_cl_fetch = git
pkg_cl_repo = https://github.com/tonyrog/cl
pkg_cl_commit = master
PACKAGES += cloudi_core
pkg_cloudi_core_name = cloudi_core
pkg_cloudi_core_description = CloudI internal service runtime
pkg_cloudi_core_homepage = http://cloudi.org/
pkg_cloudi_core_fetch = git
pkg_cloudi_core_repo = https://github.com/CloudI/cloudi_core
pkg_cloudi_core_commit = master
PACKAGES += cloudi_service_api_requests
pkg_cloudi_service_api_requests_name = cloudi_service_api_requests
pkg_cloudi_service_api_requests_description = CloudI Service API requests (JSON-RPC/Erlang-term support)
pkg_cloudi_service_api_requests_homepage = http://cloudi.org/
pkg_cloudi_service_api_requests_fetch = git
pkg_cloudi_service_api_requests_repo = https://github.com/CloudI/cloudi_service_api_requests
pkg_cloudi_service_api_requests_commit = master
PACKAGES += cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_name = cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_description = Cassandra CQL CloudI Service
pkg_cloudi_service_db_cassandra_cql_homepage = http://cloudi.org/
pkg_cloudi_service_db_cassandra_cql_fetch = git
pkg_cloudi_service_db_cassandra_cql_repo = https://github.com/CloudI/cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_commit = master
PACKAGES += cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_name = cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_description = Cassandra CloudI Service
pkg_cloudi_service_db_cassandra_homepage = http://cloudi.org/
pkg_cloudi_service_db_cassandra_fetch = git
pkg_cloudi_service_db_cassandra_repo = https://github.com/CloudI/cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_commit = master
PACKAGES += cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_name = cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_description = CouchDB CloudI Service
pkg_cloudi_service_db_couchdb_homepage = http://cloudi.org/
pkg_cloudi_service_db_couchdb_fetch = git
pkg_cloudi_service_db_couchdb_repo = https://github.com/CloudI/cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_commit = master
PACKAGES += cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_name = cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_description = elasticsearch CloudI Service
pkg_cloudi_service_db_elasticsearch_homepage = http://cloudi.org/
pkg_cloudi_service_db_elasticsearch_fetch = git
pkg_cloudi_service_db_elasticsearch_repo = https://github.com/CloudI/cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_commit = master
PACKAGES += cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_name = cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_description = memcached CloudI Service
pkg_cloudi_service_db_memcached_homepage = http://cloudi.org/
pkg_cloudi_service_db_memcached_fetch = git
pkg_cloudi_service_db_memcached_repo = https://github.com/CloudI/cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_commit = master
PACKAGES += cloudi_service_db
pkg_cloudi_service_db_name = cloudi_service_db
pkg_cloudi_service_db_description = CloudI Database (in-memory/testing/generic)
pkg_cloudi_service_db_homepage = http://cloudi.org/
pkg_cloudi_service_db_fetch = git
pkg_cloudi_service_db_repo = https://github.com/CloudI/cloudi_service_db
pkg_cloudi_service_db_commit = master
PACKAGES += cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_name = cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_description = MySQL CloudI Service
pkg_cloudi_service_db_mysql_homepage = http://cloudi.org/
pkg_cloudi_service_db_mysql_fetch = git
pkg_cloudi_service_db_mysql_repo = https://github.com/CloudI/cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_commit = master
PACKAGES += cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_name = cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_description = PostgreSQL CloudI Service
pkg_cloudi_service_db_pgsql_homepage = http://cloudi.org/
pkg_cloudi_service_db_pgsql_fetch = git
pkg_cloudi_service_db_pgsql_repo = https://github.com/CloudI/cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_commit = master
PACKAGES += cloudi_service_db_riak
pkg_cloudi_service_db_riak_name = cloudi_service_db_riak
pkg_cloudi_service_db_riak_description = Riak CloudI Service
pkg_cloudi_service_db_riak_homepage = http://cloudi.org/
pkg_cloudi_service_db_riak_fetch = git
pkg_cloudi_service_db_riak_repo = https://github.com/CloudI/cloudi_service_db_riak
pkg_cloudi_service_db_riak_commit = master
PACKAGES += cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_name = cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_description = Tokyo Tyrant CloudI Service
pkg_cloudi_service_db_tokyotyrant_homepage = http://cloudi.org/
pkg_cloudi_service_db_tokyotyrant_fetch = git
pkg_cloudi_service_db_tokyotyrant_repo = https://github.com/CloudI/cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_commit = master
PACKAGES += cloudi_service_filesystem
pkg_cloudi_service_filesystem_name = cloudi_service_filesystem
pkg_cloudi_service_filesystem_description = Filesystem CloudI Service
pkg_cloudi_service_filesystem_homepage = http://cloudi.org/
pkg_cloudi_service_filesystem_fetch = git
pkg_cloudi_service_filesystem_repo = https://github.com/CloudI/cloudi_service_filesystem
pkg_cloudi_service_filesystem_commit = master
PACKAGES += cloudi_service_http_client
pkg_cloudi_service_http_client_name = cloudi_service_http_client
pkg_cloudi_service_http_client_description = HTTP client CloudI Service
pkg_cloudi_service_http_client_homepage = http://cloudi.org/
pkg_cloudi_service_http_client_fetch = git
pkg_cloudi_service_http_client_repo = https://github.com/CloudI/cloudi_service_http_client
pkg_cloudi_service_http_client_commit = master
PACKAGES += cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_name = cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_description = cowboy HTTP/HTTPS CloudI Service
pkg_cloudi_service_http_cowboy_homepage = http://cloudi.org/
pkg_cloudi_service_http_cowboy_fetch = git
pkg_cloudi_service_http_cowboy_repo = https://github.com/CloudI/cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_commit = master
PACKAGES += cloudi_service_http_elli
pkg_cloudi_service_http_elli_name = cloudi_service_http_elli
pkg_cloudi_service_http_elli_description = elli HTTP CloudI Service
pkg_cloudi_service_http_elli_homepage = http://cloudi.org/
pkg_cloudi_service_http_elli_fetch = git
pkg_cloudi_service_http_elli_repo = https://github.com/CloudI/cloudi_service_http_elli
pkg_cloudi_service_http_elli_commit = master
PACKAGES += cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_name = cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_description = Map/Reduce CloudI Service
pkg_cloudi_service_map_reduce_homepage = http://cloudi.org/
pkg_cloudi_service_map_reduce_fetch = git
pkg_cloudi_service_map_reduce_repo = https://github.com/CloudI/cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_commit = master
PACKAGES += cloudi_service_oauth1
pkg_cloudi_service_oauth1_name = cloudi_service_oauth1
pkg_cloudi_service_oauth1_description = OAuth v1.0 CloudI Service
pkg_cloudi_service_oauth1_homepage = http://cloudi.org/
pkg_cloudi_service_oauth1_fetch = git
pkg_cloudi_service_oauth1_repo = https://github.com/CloudI/cloudi_service_oauth1
pkg_cloudi_service_oauth1_commit = master
PACKAGES += cloudi_service_queue
pkg_cloudi_service_queue_name = cloudi_service_queue
pkg_cloudi_service_queue_description = Persistent Queue Service
pkg_cloudi_service_queue_homepage = http://cloudi.org/
pkg_cloudi_service_queue_fetch = git
pkg_cloudi_service_queue_repo = https://github.com/CloudI/cloudi_service_queue
pkg_cloudi_service_queue_commit = master
PACKAGES += cloudi_service_quorum
pkg_cloudi_service_quorum_name = cloudi_service_quorum
pkg_cloudi_service_quorum_description = CloudI Quorum Service
pkg_cloudi_service_quorum_homepage = http://cloudi.org/
pkg_cloudi_service_quorum_fetch = git
pkg_cloudi_service_quorum_repo = https://github.com/CloudI/cloudi_service_quorum
pkg_cloudi_service_quorum_commit = master
PACKAGES += cloudi_service_router
pkg_cloudi_service_router_name = cloudi_service_router
pkg_cloudi_service_router_description = CloudI Router Service
pkg_cloudi_service_router_homepage = http://cloudi.org/
pkg_cloudi_service_router_fetch = git
pkg_cloudi_service_router_repo = https://github.com/CloudI/cloudi_service_router
pkg_cloudi_service_router_commit = master
PACKAGES += cloudi_service_tcp
pkg_cloudi_service_tcp_name = cloudi_service_tcp
pkg_cloudi_service_tcp_description = TCP CloudI Service
pkg_cloudi_service_tcp_homepage = http://cloudi.org/
pkg_cloudi_service_tcp_fetch = git
pkg_cloudi_service_tcp_repo = https://github.com/CloudI/cloudi_service_tcp
pkg_cloudi_service_tcp_commit = master
PACKAGES += cloudi_service_timers
pkg_cloudi_service_timers_name = cloudi_service_timers
pkg_cloudi_service_timers_description = Timers CloudI Service
pkg_cloudi_service_timers_homepage = http://cloudi.org/
pkg_cloudi_service_timers_fetch = git
pkg_cloudi_service_timers_repo = https://github.com/CloudI/cloudi_service_timers
pkg_cloudi_service_timers_commit = master
PACKAGES += cloudi_service_udp
pkg_cloudi_service_udp_name = cloudi_service_udp
pkg_cloudi_service_udp_description = UDP CloudI Service
pkg_cloudi_service_udp_homepage = http://cloudi.org/
pkg_cloudi_service_udp_fetch = git
pkg_cloudi_service_udp_repo = https://github.com/CloudI/cloudi_service_udp
pkg_cloudi_service_udp_commit = master
PACKAGES += cloudi_service_validate
pkg_cloudi_service_validate_name = cloudi_service_validate
pkg_cloudi_service_validate_description = CloudI Validate Service
pkg_cloudi_service_validate_homepage = http://cloudi.org/
pkg_cloudi_service_validate_fetch = git
pkg_cloudi_service_validate_repo = https://github.com/CloudI/cloudi_service_validate
pkg_cloudi_service_validate_commit = master
PACKAGES += cloudi_service_zeromq
pkg_cloudi_service_zeromq_name = cloudi_service_zeromq
pkg_cloudi_service_zeromq_description = ZeroMQ CloudI Service
pkg_cloudi_service_zeromq_homepage = http://cloudi.org/
pkg_cloudi_service_zeromq_fetch = git
pkg_cloudi_service_zeromq_repo = https://github.com/CloudI/cloudi_service_zeromq
pkg_cloudi_service_zeromq_commit = master
PACKAGES += cluster_info
pkg_cluster_info_name = cluster_info
pkg_cluster_info_description = Fork of Hibari's nifty cluster_info OTP app
pkg_cluster_info_homepage = https://github.com/basho/cluster_info
pkg_cluster_info_fetch = git
pkg_cluster_info_repo = https://github.com/basho/cluster_info
pkg_cluster_info_commit = master
PACKAGES += color
pkg_color_name = color
pkg_color_description = ANSI colors for your Erlang
pkg_color_homepage = https://github.com/julianduque/erlang-color
pkg_color_fetch = git
pkg_color_repo = https://github.com/julianduque/erlang-color
pkg_color_commit = master
PACKAGES += confetti
pkg_confetti_name = confetti
pkg_confetti_description = Erlang configuration provider / application:get_env/2 on steroids
pkg_confetti_homepage = https://github.com/jtendo/confetti
pkg_confetti_fetch = git
pkg_confetti_repo = https://github.com/jtendo/confetti
pkg_confetti_commit = master
PACKAGES += couchbeam
pkg_couchbeam_name = couchbeam
pkg_couchbeam_description = Apache CouchDB client in Erlang
pkg_couchbeam_homepage = https://github.com/benoitc/couchbeam
pkg_couchbeam_fetch = git
pkg_couchbeam_repo = https://github.com/benoitc/couchbeam
pkg_couchbeam_commit = master
PACKAGES += couch
pkg_couch_name = couch
pkg_couch_description = A embeddable document oriented database compatible with Apache CouchDB
pkg_couch_homepage = https://github.com/benoitc/opencouch
pkg_couch_fetch = git
pkg_couch_repo = https://github.com/benoitc/opencouch
pkg_couch_commit = master
PACKAGES += covertool
pkg_covertool_name = covertool
pkg_covertool_description = Tool to convert Erlang cover data files into Cobertura XML reports
pkg_covertool_homepage = https://github.com/idubrov/covertool
pkg_covertool_fetch = git
pkg_covertool_repo = https://github.com/idubrov/covertool
pkg_covertool_commit = master
PACKAGES += cowboy
pkg_cowboy_name = cowboy
pkg_cowboy_description = Small, fast and modular HTTP server.
pkg_cowboy_homepage = http://ninenines.eu
pkg_cowboy_fetch = git
pkg_cowboy_repo = https://github.com/ninenines/cowboy
pkg_cowboy_commit = 1.0.1
PACKAGES += cowdb
pkg_cowdb_name = cowdb
pkg_cowdb_description = Pure Key/Value database library for Erlang Applications
pkg_cowdb_homepage = https://github.com/refuge/cowdb
pkg_cowdb_fetch = git
pkg_cowdb_repo = https://github.com/refuge/cowdb
pkg_cowdb_commit = master
PACKAGES += cowlib
pkg_cowlib_name = cowlib
pkg_cowlib_description = Support library for manipulating Web protocols.
pkg_cowlib_homepage = http://ninenines.eu
pkg_cowlib_fetch = git
pkg_cowlib_repo = https://github.com/ninenines/cowlib
pkg_cowlib_commit = 1.0.1
PACKAGES += cpg
pkg_cpg_name = cpg
pkg_cpg_description = CloudI Process Groups
pkg_cpg_homepage = https://github.com/okeuday/cpg
pkg_cpg_fetch = git
pkg_cpg_repo = https://github.com/okeuday/cpg
pkg_cpg_commit = master
PACKAGES += cqerl
pkg_cqerl_name = cqerl
pkg_cqerl_description = Native Erlang CQL client for Cassandra
pkg_cqerl_homepage = https://matehat.github.io/cqerl/
pkg_cqerl_fetch = git
pkg_cqerl_repo = https://github.com/matehat/cqerl
pkg_cqerl_commit = master
PACKAGES += cr
pkg_cr_name = cr
pkg_cr_description = Chain Replication
pkg_cr_homepage = https://synrc.com/apps/cr/doc/cr.htm
pkg_cr_fetch = git
pkg_cr_repo = https://github.com/spawnproc/cr
pkg_cr_commit = master
PACKAGES += cuttlefish
pkg_cuttlefish_name = cuttlefish
pkg_cuttlefish_description = never lose your childlike sense of wonder baby cuttlefish, promise me?
pkg_cuttlefish_homepage = https://github.com/basho/cuttlefish
pkg_cuttlefish_fetch = git
pkg_cuttlefish_repo = https://github.com/basho/cuttlefish
pkg_cuttlefish_commit = master
PACKAGES += damocles
pkg_damocles_name = damocles
pkg_damocles_description = Erlang library for generating adversarial network conditions for QAing distributed applications/systems on a single Linux box.
pkg_damocles_homepage = https://github.com/lostcolony/damocles
pkg_damocles_fetch = git
pkg_damocles_repo = https://github.com/lostcolony/damocles
pkg_damocles_commit = master
PACKAGES += debbie
pkg_debbie_name = debbie
pkg_debbie_description = .DEB Built In Erlang
pkg_debbie_homepage = https://github.com/crownedgrouse/debbie
pkg_debbie_fetch = git
pkg_debbie_repo = https://github.com/crownedgrouse/debbie
pkg_debbie_commit = master
PACKAGES += decimal
pkg_decimal_name = decimal
pkg_decimal_description = An Erlang decimal arithmetic library
pkg_decimal_homepage = https://github.com/tim/erlang-decimal
pkg_decimal_fetch = git
pkg_decimal_repo = https://github.com/tim/erlang-decimal
pkg_decimal_commit = master
PACKAGES += detergent
pkg_detergent_name = detergent
pkg_detergent_description = An emulsifying Erlang SOAP library
pkg_detergent_homepage = https://github.com/devinus/detergent
pkg_detergent_fetch = git
pkg_detergent_repo = https://github.com/devinus/detergent
pkg_detergent_commit = master
PACKAGES += detest
pkg_detest_name = detest
pkg_detest_description = Tool for running tests on a cluster of erlang nodes
pkg_detest_homepage = https://github.com/biokoda/detest
pkg_detest_fetch = git
pkg_detest_repo = https://github.com/biokoda/detest
pkg_detest_commit = master
PACKAGES += dh_date
pkg_dh_date_name = dh_date
pkg_dh_date_description = Date formatting / parsing library for erlang
pkg_dh_date_homepage = https://github.com/daleharvey/dh_date
pkg_dh_date_fetch = git
pkg_dh_date_repo = https://github.com/daleharvey/dh_date
pkg_dh_date_commit = master
PACKAGES += dhtcrawler
pkg_dhtcrawler_name = dhtcrawler
pkg_dhtcrawler_description = dhtcrawler is a DHT crawler written in erlang. It can join a DHT network and crawl many P2P torrents.
pkg_dhtcrawler_homepage = https://github.com/kevinlynx/dhtcrawler
pkg_dhtcrawler_fetch = git
pkg_dhtcrawler_repo = https://github.com/kevinlynx/dhtcrawler
pkg_dhtcrawler_commit = master
PACKAGES += dirbusterl
pkg_dirbusterl_name = dirbusterl
pkg_dirbusterl_description = DirBuster successor in Erlang
pkg_dirbusterl_homepage = https://github.com/silentsignal/DirBustErl
pkg_dirbusterl_fetch = git
pkg_dirbusterl_repo = https://github.com/silentsignal/DirBustErl
pkg_dirbusterl_commit = master
PACKAGES += dispcount
pkg_dispcount_name = dispcount
pkg_dispcount_description = Erlang task dispatcher based on ETS counters.
pkg_dispcount_homepage = https://github.com/ferd/dispcount
pkg_dispcount_fetch = git
pkg_dispcount_repo = https://github.com/ferd/dispcount
pkg_dispcount_commit = master
PACKAGES += dlhttpc
pkg_dlhttpc_name = dlhttpc
pkg_dlhttpc_description = dispcount-based lhttpc fork for massive amounts of requests to limited endpoints
pkg_dlhttpc_homepage = https://github.com/ferd/dlhttpc
pkg_dlhttpc_fetch = git
pkg_dlhttpc_repo = https://github.com/ferd/dlhttpc
pkg_dlhttpc_commit = master
PACKAGES += dns
pkg_dns_name = dns
pkg_dns_description = Erlang DNS library
pkg_dns_homepage = https://github.com/aetrion/dns_erlang
pkg_dns_fetch = git
pkg_dns_repo = https://github.com/aetrion/dns_erlang
pkg_dns_commit = master
PACKAGES += dnssd
pkg_dnssd_name = dnssd
pkg_dnssd_description = Erlang interface to Apple's Bonjour D NS Service Discovery implementation
pkg_dnssd_homepage = https://github.com/benoitc/dnssd_erlang
pkg_dnssd_fetch = git
pkg_dnssd_repo = https://github.com/benoitc/dnssd_erlang
pkg_dnssd_commit = master
PACKAGES += dtl
pkg_dtl_name = dtl
pkg_dtl_description = Django Template Language: A full-featured port of the Django template engine to Erlang.
pkg_dtl_homepage = https://github.com/oinksoft/dtl
pkg_dtl_fetch = git
pkg_dtl_repo = https://github.com/oinksoft/dtl
pkg_dtl_commit = master
PACKAGES += dynamic_compile
pkg_dynamic_compile_name = dynamic_compile
pkg_dynamic_compile_description = compile and load erlang modules from string input
pkg_dynamic_compile_homepage = https://github.com/jkvor/dynamic_compile
pkg_dynamic_compile_fetch = git
pkg_dynamic_compile_repo = https://github.com/jkvor/dynamic_compile
pkg_dynamic_compile_commit = master
PACKAGES += e2
pkg_e2_name = e2
pkg_e2_description = Library to simply writing correct OTP applications.
pkg_e2_homepage = http://e2project.org
pkg_e2_fetch = git
pkg_e2_repo = https://github.com/gar1t/e2
pkg_e2_commit = master
PACKAGES += eamf
pkg_eamf_name = eamf
pkg_eamf_description = eAMF provides Action Message Format (AMF) support for Erlang
pkg_eamf_homepage = https://github.com/mrinalwadhwa/eamf
pkg_eamf_fetch = git
pkg_eamf_repo = https://github.com/mrinalwadhwa/eamf
pkg_eamf_commit = master
PACKAGES += eavro
pkg_eavro_name = eavro
pkg_eavro_description = Apache Avro encoder/decoder
pkg_eavro_homepage = https://github.com/SIfoxDevTeam/eavro
pkg_eavro_fetch = git
pkg_eavro_repo = https://github.com/SIfoxDevTeam/eavro
pkg_eavro_commit = master
PACKAGES += ecapnp
pkg_ecapnp_name = ecapnp
pkg_ecapnp_description = Cap'n Proto library for Erlang
pkg_ecapnp_homepage = https://github.com/kaos/ecapnp
pkg_ecapnp_fetch = git
pkg_ecapnp_repo = https://github.com/kaos/ecapnp
pkg_ecapnp_commit = master
PACKAGES += econfig
pkg_econfig_name = econfig
pkg_econfig_description = simple Erlang config handler using INI files
pkg_econfig_homepage = https://github.com/benoitc/econfig
pkg_econfig_fetch = git
pkg_econfig_repo = https://github.com/benoitc/econfig
pkg_econfig_commit = master
PACKAGES += edate
pkg_edate_name = edate
pkg_edate_description = date manipulation library for erlang
pkg_edate_homepage = https://github.com/dweldon/edate
pkg_edate_fetch = git
pkg_edate_repo = https://github.com/dweldon/edate
pkg_edate_commit = master
PACKAGES += edgar
pkg_edgar_name = edgar
pkg_edgar_description = Erlang Does GNU AR
pkg_edgar_homepage = https://github.com/crownedgrouse/edgar
pkg_edgar_fetch = git
pkg_edgar_repo = https://github.com/crownedgrouse/edgar
pkg_edgar_commit = master
PACKAGES += edis
pkg_edis_name = edis
pkg_edis_description = An Erlang implementation of Redis KV Store
pkg_edis_homepage = http://inaka.github.com/edis/
pkg_edis_fetch = git
pkg_edis_repo = https://github.com/inaka/edis
pkg_edis_commit = master
PACKAGES += edns
pkg_edns_name = edns
pkg_edns_description = Erlang/OTP DNS server
pkg_edns_homepage = https://github.com/hcvst/erlang-dns
pkg_edns_fetch = git
pkg_edns_repo = https://github.com/hcvst/erlang-dns
pkg_edns_commit = master
PACKAGES += edown
pkg_edown_name = edown
pkg_edown_description = EDoc extension for generating Github-flavored Markdown
pkg_edown_homepage = https://github.com/uwiger/edown
pkg_edown_fetch = git
pkg_edown_repo = https://github.com/uwiger/edown
pkg_edown_commit = master
PACKAGES += eep_app
pkg_eep_app_name = eep_app
pkg_eep_app_description = Embedded Event Processing
pkg_eep_app_homepage = https://github.com/darach/eep-erl
pkg_eep_app_fetch = git
pkg_eep_app_repo = https://github.com/darach/eep-erl
pkg_eep_app_commit = master
PACKAGES += eep
pkg_eep_name = eep
pkg_eep_description = Erlang Easy Profiling (eep) application provides a way to analyze application performance and call hierarchy
pkg_eep_homepage = https://github.com/virtan/eep
pkg_eep_fetch = git
pkg_eep_repo = https://github.com/virtan/eep
pkg_eep_commit = master
PACKAGES += efene
pkg_efene_name = efene
pkg_efene_description = Alternative syntax for the Erlang Programming Language focusing on simplicity, ease of use and programmer UX
pkg_efene_homepage = https://github.com/efene/efene
pkg_efene_fetch = git
pkg_efene_repo = https://github.com/efene/efene
pkg_efene_commit = master
PACKAGES += eganglia
pkg_eganglia_name = eganglia
pkg_eganglia_description = Erlang library to interact with Ganglia
pkg_eganglia_homepage = https://github.com/inaka/eganglia
pkg_eganglia_fetch = git
pkg_eganglia_repo = https://github.com/inaka/eganglia
pkg_eganglia_commit = v0.9.1
PACKAGES += egeoip
pkg_egeoip_name = egeoip
pkg_egeoip_description = Erlang IP Geolocation module, currently supporting the MaxMind GeoLite City Database.
pkg_egeoip_homepage = https://github.com/mochi/egeoip
pkg_egeoip_fetch = git
pkg_egeoip_repo = https://github.com/mochi/egeoip
pkg_egeoip_commit = master
PACKAGES += ehsa
pkg_ehsa_name = ehsa
pkg_ehsa_description = Erlang HTTP server basic and digest authentication modules
pkg_ehsa_homepage = https://bitbucket.org/a12n/ehsa
pkg_ehsa_fetch = hg
pkg_ehsa_repo = https://bitbucket.org/a12n/ehsa
pkg_ehsa_commit = 2.0.4
PACKAGES += ejabberd
pkg_ejabberd_name = ejabberd
pkg_ejabberd_description = Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform
pkg_ejabberd_homepage = https://github.com/processone/ejabberd
pkg_ejabberd_fetch = git
pkg_ejabberd_repo = https://github.com/processone/ejabberd
pkg_ejabberd_commit = master
PACKAGES += ej
pkg_ej_name = ej
pkg_ej_description = Helper module for working with Erlang terms representing JSON
pkg_ej_homepage = https://github.com/seth/ej
pkg_ej_fetch = git
pkg_ej_repo = https://github.com/seth/ej
pkg_ej_commit = master
PACKAGES += ejwt
pkg_ejwt_name = ejwt
pkg_ejwt_description = erlang library for JSON Web Token
pkg_ejwt_homepage = https://github.com/artefactop/ejwt
pkg_ejwt_fetch = git
pkg_ejwt_repo = https://github.com/artefactop/ejwt
pkg_ejwt_commit = master
PACKAGES += ekaf
pkg_ekaf_name = ekaf
pkg_ekaf_description = A minimal, high-performance Kafka client in Erlang.
pkg_ekaf_homepage = https://github.com/helpshift/ekaf
pkg_ekaf_fetch = git
pkg_ekaf_repo = https://github.com/helpshift/ekaf
pkg_ekaf_commit = master
PACKAGES += elarm
pkg_elarm_name = elarm
pkg_elarm_description = Alarm Manager for Erlang.
pkg_elarm_homepage = https://github.com/esl/elarm
pkg_elarm_fetch = git
pkg_elarm_repo = https://github.com/esl/elarm
pkg_elarm_commit = master
PACKAGES += eleveldb
pkg_eleveldb_name = eleveldb
pkg_eleveldb_description = Erlang LevelDB API
pkg_eleveldb_homepage = https://github.com/basho/eleveldb
pkg_eleveldb_fetch = git
pkg_eleveldb_repo = https://github.com/basho/eleveldb
pkg_eleveldb_commit = master
PACKAGES += elli
pkg_elli_name = elli
pkg_elli_description = Simple, robust and performant Erlang web server
pkg_elli_homepage = https://github.com/knutin/elli
pkg_elli_fetch = git
pkg_elli_repo = https://github.com/knutin/elli
pkg_elli_commit = master
PACKAGES += elvis
pkg_elvis_name = elvis
pkg_elvis_description = Erlang Style Reviewer
pkg_elvis_homepage = https://github.com/inaka/elvis
pkg_elvis_fetch = git
pkg_elvis_repo = https://github.com/inaka/elvis
pkg_elvis_commit = 0.2.4
PACKAGES += emagick
pkg_emagick_name = emagick
pkg_emagick_description = Wrapper for Graphics/ImageMagick command line tool.
pkg_emagick_homepage = https://github.com/kivra/emagick
pkg_emagick_fetch = git
pkg_emagick_repo = https://github.com/kivra/emagick
pkg_emagick_commit = master
PACKAGES += emysql
pkg_emysql_name = emysql
pkg_emysql_description = Stable, pure Erlang MySQL driver.
pkg_emysql_homepage = https://github.com/Eonblast/Emysql
pkg_emysql_fetch = git
pkg_emysql_repo = https://github.com/Eonblast/Emysql
pkg_emysql_commit = master
PACKAGES += enm
pkg_enm_name = enm
pkg_enm_description = Erlang driver for nanomsg
pkg_enm_homepage = https://github.com/basho/enm
pkg_enm_fetch = git
pkg_enm_repo = https://github.com/basho/enm
pkg_enm_commit = master
PACKAGES += entop
pkg_entop_name = entop
pkg_entop_description = A top-like tool for monitoring an Erlang node
pkg_entop_homepage = https://github.com/mazenharake/entop
pkg_entop_fetch = git
pkg_entop_repo = https://github.com/mazenharake/entop
pkg_entop_commit = master
PACKAGES += epcap
pkg_epcap_name = epcap
pkg_epcap_description = Erlang packet capture interface using pcap
pkg_epcap_homepage = https://github.com/msantos/epcap
pkg_epcap_fetch = git
pkg_epcap_repo = https://github.com/msantos/epcap
pkg_epcap_commit = master
PACKAGES += eper
pkg_eper_name = eper
pkg_eper_description = Erlang performance and debugging tools.
pkg_eper_homepage = https://github.com/massemanet/eper
pkg_eper_fetch = git
pkg_eper_repo = https://github.com/massemanet/eper
pkg_eper_commit = master
PACKAGES += epgsql
pkg_epgsql_name = epgsql
pkg_epgsql_description = Erlang PostgreSQL client library.
pkg_epgsql_homepage = https://github.com/epgsql/epgsql
pkg_epgsql_fetch = git
pkg_epgsql_repo = https://github.com/epgsql/epgsql
pkg_epgsql_commit = master
PACKAGES += episcina
pkg_episcina_name = episcina
pkg_episcina_description = A simple non intrusive resource pool for connections
pkg_episcina_homepage = https://github.com/erlware/episcina
pkg_episcina_fetch = git
pkg_episcina_repo = https://github.com/erlware/episcina
pkg_episcina_commit = master
PACKAGES += eplot
pkg_eplot_name = eplot
pkg_eplot_description = A plot engine written in erlang.
pkg_eplot_homepage = https://github.com/psyeugenic/eplot
pkg_eplot_fetch = git
pkg_eplot_repo = https://github.com/psyeugenic/eplot
pkg_eplot_commit = master
PACKAGES += epocxy
pkg_epocxy_name = epocxy
pkg_epocxy_description = Erlang Patterns of Concurrency
pkg_epocxy_homepage = https://github.com/duomark/epocxy
pkg_epocxy_fetch = git
pkg_epocxy_repo = https://github.com/duomark/epocxy
pkg_epocxy_commit = master
PACKAGES += epubnub
pkg_epubnub_name = epubnub
pkg_epubnub_description = Erlang PubNub API
pkg_epubnub_homepage = https://github.com/tsloughter/epubnub
pkg_epubnub_fetch = git
pkg_epubnub_repo = https://github.com/tsloughter/epubnub
pkg_epubnub_commit = master
PACKAGES += eqm
pkg_eqm_name = eqm
pkg_eqm_description = Erlang pub sub with supply-demand channels
pkg_eqm_homepage = https://github.com/loucash/eqm
pkg_eqm_fetch = git
pkg_eqm_repo = https://github.com/loucash/eqm
pkg_eqm_commit = master
PACKAGES += eredis
pkg_eredis_name = eredis
pkg_eredis_description = Erlang Redis client
pkg_eredis_homepage = https://github.com/wooga/eredis
pkg_eredis_fetch = git
pkg_eredis_repo = https://github.com/wooga/eredis
pkg_eredis_commit = master
PACKAGES += eredis_pool
pkg_eredis_pool_name = eredis_pool
pkg_eredis_pool_description = eredis_pool is Pool of Redis clients, using eredis and poolboy.
pkg_eredis_pool_homepage = https://github.com/hiroeorz/eredis_pool
pkg_eredis_pool_fetch = git
pkg_eredis_pool_repo = https://github.com/hiroeorz/eredis_pool
pkg_eredis_pool_commit = master
PACKAGES += erlang_cep
pkg_erlang_cep_name = erlang_cep
pkg_erlang_cep_description = A basic CEP package written in erlang
pkg_erlang_cep_homepage = https://github.com/danmacklin/erlang_cep
pkg_erlang_cep_fetch = git
pkg_erlang_cep_repo = https://github.com/danmacklin/erlang_cep
pkg_erlang_cep_commit = master
PACKAGES += erlang_js
pkg_erlang_js_name = erlang_js
pkg_erlang_js_description = A linked-in driver for Erlang to Mozilla's Spidermonkey Javascript runtime.
pkg_erlang_js_homepage = https://github.com/basho/erlang_js
pkg_erlang_js_fetch = git
pkg_erlang_js_repo = https://github.com/basho/erlang_js
pkg_erlang_js_commit = master
PACKAGES += erlang_localtime
pkg_erlang_localtime_name = erlang_localtime
pkg_erlang_localtime_description = Erlang library for conversion from one local time to another
pkg_erlang_localtime_homepage = https://github.com/dmitryme/erlang_localtime
pkg_erlang_localtime_fetch = git
pkg_erlang_localtime_repo = https://github.com/dmitryme/erlang_localtime
pkg_erlang_localtime_commit = master
PACKAGES += erlang_smtp
pkg_erlang_smtp_name = erlang_smtp
pkg_erlang_smtp_description = Erlang SMTP and POP3 server code.
pkg_erlang_smtp_homepage = https://github.com/tonyg/erlang-smtp
pkg_erlang_smtp_fetch = git
pkg_erlang_smtp_repo = https://github.com/tonyg/erlang-smtp
pkg_erlang_smtp_commit = master
PACKAGES += erlang_term
pkg_erlang_term_name = erlang_term
pkg_erlang_term_description = Erlang Term Info
pkg_erlang_term_homepage = https://github.com/okeuday/erlang_term
pkg_erlang_term_fetch = git
pkg_erlang_term_repo = https://github.com/okeuday/erlang_term
pkg_erlang_term_commit = master
PACKAGES += erlastic_search
pkg_erlastic_search_name = erlastic_search
pkg_erlastic_search_description = An Erlang app for communicating with Elastic Search's rest interface.
pkg_erlastic_search_homepage = https://github.com/tsloughter/erlastic_search
pkg_erlastic_search_fetch = git
pkg_erlastic_search_repo = https://github.com/tsloughter/erlastic_search
pkg_erlastic_search_commit = master
PACKAGES += erlasticsearch
pkg_erlasticsearch_name = erlasticsearch
pkg_erlasticsearch_description = Erlang thrift interface to elastic_search
pkg_erlasticsearch_homepage = https://github.com/dieswaytoofast/erlasticsearch
pkg_erlasticsearch_fetch = git
pkg_erlasticsearch_repo = https://github.com/dieswaytoofast/erlasticsearch
pkg_erlasticsearch_commit = master
PACKAGES += erlbrake
pkg_erlbrake_name = erlbrake
pkg_erlbrake_description = Erlang Airbrake notification client
pkg_erlbrake_homepage = https://github.com/kenpratt/erlbrake
pkg_erlbrake_fetch = git
pkg_erlbrake_repo = https://github.com/kenpratt/erlbrake
pkg_erlbrake_commit = master
PACKAGES += erlcloud
pkg_erlcloud_name = erlcloud
pkg_erlcloud_description = Cloud Computing library for erlang (Amazon EC2, S3, SQS, SimpleDB, Mechanical Turk, ELB)
pkg_erlcloud_homepage = https://github.com/gleber/erlcloud
pkg_erlcloud_fetch = git
pkg_erlcloud_repo = https://github.com/gleber/erlcloud
pkg_erlcloud_commit = master
PACKAGES += erlcron
pkg_erlcron_name = erlcron
pkg_erlcron_description = Erlang cronish system
pkg_erlcron_homepage = https://github.com/erlware/erlcron
pkg_erlcron_fetch = git
pkg_erlcron_repo = https://github.com/erlware/erlcron
pkg_erlcron_commit = master
PACKAGES += erldb
pkg_erldb_name = erldb
pkg_erldb_description = ORM (Object-relational mapping) application implemented in Erlang
pkg_erldb_homepage = http://erldb.org
pkg_erldb_fetch = git
pkg_erldb_repo = https://github.com/erldb/erldb
pkg_erldb_commit = master
PACKAGES += erldis
pkg_erldis_name = erldis
pkg_erldis_description = redis erlang client library
pkg_erldis_homepage = https://github.com/cstar/erldis
pkg_erldis_fetch = git
pkg_erldis_repo = https://github.com/cstar/erldis
pkg_erldis_commit = master
PACKAGES += erldns
pkg_erldns_name = erldns
pkg_erldns_description = DNS server, in erlang.
pkg_erldns_homepage = https://github.com/aetrion/erl-dns
pkg_erldns_fetch = git
pkg_erldns_repo = https://github.com/aetrion/erl-dns
pkg_erldns_commit = master
PACKAGES += erldocker
pkg_erldocker_name = erldocker
pkg_erldocker_description = Docker Remote API client for Erlang
pkg_erldocker_homepage = https://github.com/proger/erldocker
pkg_erldocker_fetch = git
pkg_erldocker_repo = https://github.com/proger/erldocker
pkg_erldocker_commit = master
PACKAGES += erlfsmon
pkg_erlfsmon_name = erlfsmon
pkg_erlfsmon_description = Erlang filesystem event watcher for Linux and OSX
pkg_erlfsmon_homepage = https://github.com/proger/erlfsmon
pkg_erlfsmon_fetch = git
pkg_erlfsmon_repo = https://github.com/proger/erlfsmon
pkg_erlfsmon_commit = master
PACKAGES += erlgit
pkg_erlgit_name = erlgit
pkg_erlgit_description = Erlang convenience wrapper around git executable
pkg_erlgit_homepage = https://github.com/gleber/erlgit
pkg_erlgit_fetch = git
pkg_erlgit_repo = https://github.com/gleber/erlgit
pkg_erlgit_commit = master
PACKAGES += erlguten
pkg_erlguten_name = erlguten
pkg_erlguten_description = ErlGuten is a system for high-quality typesetting, written purely in Erlang.
pkg_erlguten_homepage = https://github.com/richcarl/erlguten
pkg_erlguten_fetch = git
pkg_erlguten_repo = https://github.com/richcarl/erlguten
pkg_erlguten_commit = master
PACKAGES += erlmc
pkg_erlmc_name = erlmc
pkg_erlmc_description = Erlang memcached binary protocol client
pkg_erlmc_homepage = https://github.com/jkvor/erlmc
pkg_erlmc_fetch = git
pkg_erlmc_repo = https://github.com/jkvor/erlmc
pkg_erlmc_commit = master
PACKAGES += erlmongo
pkg_erlmongo_name = erlmongo
pkg_erlmongo_description = Record based Erlang driver for MongoDB with gridfs support
pkg_erlmongo_homepage = https://github.com/SergejJurecko/erlmongo
pkg_erlmongo_fetch = git
pkg_erlmongo_repo = https://github.com/SergejJurecko/erlmongo
pkg_erlmongo_commit = master
PACKAGES += erlog
pkg_erlog_name = erlog
pkg_erlog_description = Prolog interpreter in and for Erlang
pkg_erlog_homepage = https://github.com/rvirding/erlog
pkg_erlog_fetch = git
pkg_erlog_repo = https://github.com/rvirding/erlog
pkg_erlog_commit = master
PACKAGES += erlpass
pkg_erlpass_name = erlpass
pkg_erlpass_description = A library to handle password hashing and changing in a safe manner, independent from any kind of storage whatsoever.
pkg_erlpass_homepage = https://github.com/ferd/erlpass
pkg_erlpass_fetch = git
pkg_erlpass_repo = https://github.com/ferd/erlpass
pkg_erlpass_commit = master
PACKAGES += erlport
pkg_erlport_name = erlport
pkg_erlport_description = ErlPort - connect Erlang to other languages
pkg_erlport_homepage = https://github.com/hdima/erlport
pkg_erlport_fetch = git
pkg_erlport_repo = https://github.com/hdima/erlport
pkg_erlport_commit = master
PACKAGES += erlsha2
pkg_erlsha2_name = erlsha2
pkg_erlsha2_description = SHA-224, SHA-256, SHA-384, SHA-512 implemented in Erlang NIFs.
pkg_erlsha2_homepage = https://github.com/vinoski/erlsha2
pkg_erlsha2_fetch = git
pkg_erlsha2_repo = https://github.com/vinoski/erlsha2
pkg_erlsha2_commit = master
PACKAGES += erlsh
pkg_erlsh_name = erlsh
pkg_erlsh_description = Erlang shell tools
pkg_erlsh_homepage = https://github.com/proger/erlsh
pkg_erlsh_fetch = git
pkg_erlsh_repo = https://github.com/proger/erlsh
pkg_erlsh_commit = master
PACKAGES += erlsom
pkg_erlsom_name = erlsom
pkg_erlsom_description = XML parser for Erlang
pkg_erlsom_homepage = https://github.com/willemdj/erlsom
pkg_erlsom_fetch = git
pkg_erlsom_repo = https://github.com/willemdj/erlsom
pkg_erlsom_commit = master
PACKAGES += erl_streams
pkg_erl_streams_name = erl_streams
pkg_erl_streams_description = Streams in Erlang
pkg_erl_streams_homepage = https://github.com/epappas/erl_streams
pkg_erl_streams_fetch = git
pkg_erl_streams_repo = https://github.com/epappas/erl_streams
pkg_erl_streams_commit = master
PACKAGES += erlubi
pkg_erlubi_name = erlubi
pkg_erlubi_description = Ubigraph Erlang Client (and Process Visualizer)
pkg_erlubi_homepage = https://github.com/krestenkrab/erlubi
pkg_erlubi_fetch = git
pkg_erlubi_repo = https://github.com/krestenkrab/erlubi
pkg_erlubi_commit = master
PACKAGES += erlvolt
pkg_erlvolt_name = erlvolt
pkg_erlvolt_description = VoltDB Erlang Client Driver
pkg_erlvolt_homepage = https://github.com/VoltDB/voltdb-client-erlang
pkg_erlvolt_fetch = git
pkg_erlvolt_repo = https://github.com/VoltDB/voltdb-client-erlang
pkg_erlvolt_commit = master
PACKAGES += erlware_commons
pkg_erlware_commons_name = erlware_commons
pkg_erlware_commons_description = Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components.
pkg_erlware_commons_homepage = https://github.com/erlware/erlware_commons
pkg_erlware_commons_fetch = git
pkg_erlware_commons_repo = https://github.com/erlware/erlware_commons
pkg_erlware_commons_commit = master
PACKAGES += erlydtl
pkg_erlydtl_name = erlydtl
pkg_erlydtl_description = Django Template Language for Erlang.
pkg_erlydtl_homepage = https://github.com/erlydtl/erlydtl
pkg_erlydtl_fetch = git
pkg_erlydtl_repo = https://github.com/erlydtl/erlydtl
pkg_erlydtl_commit = master
PACKAGES += errd
pkg_errd_name = errd
pkg_errd_description = Erlang RRDTool library
pkg_errd_homepage = https://github.com/archaelus/errd
pkg_errd_fetch = git
pkg_errd_repo = https://github.com/archaelus/errd
pkg_errd_commit = master
PACKAGES += erserve
pkg_erserve_name = erserve
pkg_erserve_description = Erlang/Rserve communication interface
pkg_erserve_homepage = https://github.com/del/erserve
pkg_erserve_fetch = git
pkg_erserve_repo = https://github.com/del/erserve
pkg_erserve_commit = master
PACKAGES += erwa
pkg_erwa_name = erwa
pkg_erwa_description = A WAMP router and client written in Erlang.
pkg_erwa_homepage = https://github.com/bwegh/erwa
pkg_erwa_fetch = git
pkg_erwa_repo = https://github.com/bwegh/erwa
pkg_erwa_commit = 0.1.1
PACKAGES += espec
pkg_espec_name = espec
pkg_espec_description = ESpec: Behaviour driven development framework for Erlang
pkg_espec_homepage = https://github.com/lucaspiller/espec
pkg_espec_fetch = git
pkg_espec_repo = https://github.com/lucaspiller/espec
pkg_espec_commit = master
PACKAGES += estatsd
pkg_estatsd_name = estatsd
pkg_estatsd_description = Erlang stats aggregation app that periodically flushes data to graphite
pkg_estatsd_homepage = https://github.com/RJ/estatsd
pkg_estatsd_fetch = git
pkg_estatsd_repo = https://github.com/RJ/estatsd
pkg_estatsd_commit = master
PACKAGES += etap
pkg_etap_name = etap
pkg_etap_description = etap is a simple erlang testing library that provides TAP compliant output.
pkg_etap_homepage = https://github.com/ngerakines/etap
pkg_etap_fetch = git
pkg_etap_repo = https://github.com/ngerakines/etap
pkg_etap_commit = master
PACKAGES += etest_http
pkg_etest_http_name = etest_http
pkg_etest_http_description = etest Assertions around HTTP (client-side)
pkg_etest_http_homepage = https://github.com/wooga/etest_http
pkg_etest_http_fetch = git
pkg_etest_http_repo = https://github.com/wooga/etest_http
pkg_etest_http_commit = master
PACKAGES += etest
pkg_etest_name = etest
pkg_etest_description = A lightweight, convention over configuration test framework for Erlang
pkg_etest_homepage = https://github.com/wooga/etest
pkg_etest_fetch = git
pkg_etest_repo = https://github.com/wooga/etest
pkg_etest_commit = master
PACKAGES += etoml
pkg_etoml_name = etoml
pkg_etoml_description = TOML language erlang parser
pkg_etoml_homepage = https://github.com/kalta/etoml
pkg_etoml_fetch = git
pkg_etoml_repo = https://github.com/kalta/etoml
pkg_etoml_commit = master
PACKAGES += eunit_formatters
pkg_eunit_formatters_name = eunit_formatters
pkg_eunit_formatters_description = Because eunit's output sucks. Let's make it better.
pkg_eunit_formatters_homepage = https://github.com/seancribbs/eunit_formatters
pkg_eunit_formatters_fetch = git
pkg_eunit_formatters_repo = https://github.com/seancribbs/eunit_formatters
pkg_eunit_formatters_commit = master
PACKAGES += eunit
pkg_eunit_name = eunit
pkg_eunit_description = The EUnit lightweight unit testing framework for Erlang - this is the canonical development repository.
pkg_eunit_homepage = https://github.com/richcarl/eunit
pkg_eunit_fetch = git
pkg_eunit_repo = https://github.com/richcarl/eunit
pkg_eunit_commit = master
PACKAGES += euthanasia
pkg_euthanasia_name = euthanasia
pkg_euthanasia_description = Merciful killer for your Erlang processes
pkg_euthanasia_homepage = https://github.com/doubleyou/euthanasia
pkg_euthanasia_fetch = git
pkg_euthanasia_repo = https://github.com/doubleyou/euthanasia
pkg_euthanasia_commit = master
PACKAGES += evum
pkg_evum_name = evum
pkg_evum_description = Spawn Linux VMs as Erlang processes in the Erlang VM
pkg_evum_homepage = https://github.com/msantos/evum
pkg_evum_fetch = git
pkg_evum_repo = https://github.com/msantos/evum
pkg_evum_commit = master
PACKAGES += exec
pkg_exec_name = exec
pkg_exec_description = Execute and control OS processes from Erlang/OTP.
pkg_exec_homepage = http://saleyn.github.com/erlexec
pkg_exec_fetch = git
pkg_exec_repo = https://github.com/saleyn/erlexec
pkg_exec_commit = master
PACKAGES += exml
pkg_exml_name = exml
pkg_exml_description = XML parsing library in Erlang
pkg_exml_homepage = https://github.com/paulgray/exml
pkg_exml_fetch = git
pkg_exml_repo = https://github.com/paulgray/exml
pkg_exml_commit = master
PACKAGES += exometer
pkg_exometer_name = exometer
pkg_exometer_description = Basic measurement objects and probe behavior
pkg_exometer_homepage = https://github.com/Feuerlabs/exometer
pkg_exometer_fetch = git
pkg_exometer_repo = https://github.com/Feuerlabs/exometer
pkg_exometer_commit = 1.2
PACKAGES += exs1024
pkg_exs1024_name = exs1024
pkg_exs1024_description = Xorshift1024star pseudo random number generator for Erlang.
pkg_exs1024_homepage = https://github.com/jj1bdx/exs1024
pkg_exs1024_fetch = git
pkg_exs1024_repo = https://github.com/jj1bdx/exs1024
pkg_exs1024_commit = master
PACKAGES += exs64
pkg_exs64_name = exs64
pkg_exs64_description = Xorshift64star pseudo random number generator for Erlang.
pkg_exs64_homepage = https://github.com/jj1bdx/exs64
pkg_exs64_fetch = git
pkg_exs64_repo = https://github.com/jj1bdx/exs64
pkg_exs64_commit = master
PACKAGES += exsplus116
pkg_exsplus116_name = exsplus116
pkg_exsplus116_description = Xorshift116plus for Erlang
pkg_exsplus116_homepage = https://github.com/jj1bdx/exsplus116
pkg_exsplus116_fetch = git
pkg_exsplus116_repo = https://github.com/jj1bdx/exsplus116
pkg_exsplus116_commit = master
PACKAGES += exsplus128
pkg_exsplus128_name = exsplus128
pkg_exsplus128_description = Xorshift128plus pseudo random number generator for Erlang.
pkg_exsplus128_homepage = https://github.com/jj1bdx/exsplus128
pkg_exsplus128_fetch = git
pkg_exsplus128_repo = https://github.com/jj1bdx/exsplus128
pkg_exsplus128_commit = master
PACKAGES += ezmq
pkg_ezmq_name = ezmq
pkg_ezmq_description = zMQ implemented in Erlang
pkg_ezmq_homepage = https://github.com/RoadRunnr/ezmq
pkg_ezmq_fetch = git
pkg_ezmq_repo = https://github.com/RoadRunnr/ezmq
pkg_ezmq_commit = master
PACKAGES += ezmtp
pkg_ezmtp_name = ezmtp
pkg_ezmtp_description = ZMTP protocol in pure Erlang.
pkg_ezmtp_homepage = https://github.com/a13x/ezmtp
pkg_ezmtp_fetch = git
pkg_ezmtp_repo = https://github.com/a13x/ezmtp
pkg_ezmtp_commit = master
PACKAGES += fast_disk_log
pkg_fast_disk_log_name = fast_disk_log
pkg_fast_disk_log_description = Pool-based asynchronous Erlang disk logger
pkg_fast_disk_log_homepage = https://github.com/lpgauth/fast_disk_log
pkg_fast_disk_log_fetch = git
pkg_fast_disk_log_repo = https://github.com/lpgauth/fast_disk_log
pkg_fast_disk_log_commit = master
PACKAGES += feeder
pkg_feeder_name = feeder
pkg_feeder_description = Stream parse RSS and Atom formatted XML feeds.
pkg_feeder_homepage = https://github.com/michaelnisi/feeder
pkg_feeder_fetch = git
pkg_feeder_repo = https://github.com/michaelnisi/feeder
pkg_feeder_commit = v1.4.6
PACKAGES += fix
pkg_fix_name = fix
pkg_fix_description = http://fixprotocol.org/ implementation.
pkg_fix_homepage = https://github.com/maxlapshin/fix
pkg_fix_fetch = git
pkg_fix_repo = https://github.com/maxlapshin/fix
pkg_fix_commit = master
PACKAGES += flower
pkg_flower_name = flower
pkg_flower_description = FlowER - a Erlang OpenFlow development platform
pkg_flower_homepage = https://github.com/travelping/flower
pkg_flower_fetch = git
pkg_flower_repo = https://github.com/travelping/flower
pkg_flower_commit = master
PACKAGES += fn
pkg_fn_name = fn
pkg_fn_description = Function utilities for Erlang
pkg_fn_homepage = https://github.com/reiddraper/fn
pkg_fn_fetch = git
pkg_fn_repo = https://github.com/reiddraper/fn
pkg_fn_commit = master
PACKAGES += folsom_cowboy
pkg_folsom_cowboy_name = folsom_cowboy
pkg_folsom_cowboy_description = A Cowboy based Folsom HTTP Wrapper.
pkg_folsom_cowboy_homepage = https://github.com/boundary/folsom_cowboy
pkg_folsom_cowboy_fetch = git
pkg_folsom_cowboy_repo = https://github.com/boundary/folsom_cowboy
pkg_folsom_cowboy_commit = master
PACKAGES += folsomite
pkg_folsomite_name = folsomite
pkg_folsomite_description = blow up your graphite / riemann server with folsom metrics
pkg_folsomite_homepage = https://github.com/campanja/folsomite
pkg_folsomite_fetch = git
pkg_folsomite_repo = https://github.com/campanja/folsomite
pkg_folsomite_commit = master
PACKAGES += folsom
pkg_folsom_name = folsom
pkg_folsom_description = Expose Erlang Events and Metrics
pkg_folsom_homepage = https://github.com/boundary/folsom
pkg_folsom_fetch = git
pkg_folsom_repo = https://github.com/boundary/folsom
pkg_folsom_commit = master
PACKAGES += fs
pkg_fs_name = fs
pkg_fs_description = Erlang FileSystem Listener
pkg_fs_homepage = https://github.com/synrc/fs
pkg_fs_fetch = git
pkg_fs_repo = https://github.com/synrc/fs
pkg_fs_commit = master
PACKAGES += fuse
pkg_fuse_name = fuse
pkg_fuse_description = A Circuit Breaker for Erlang
pkg_fuse_homepage = https://github.com/jlouis/fuse
pkg_fuse_fetch = git
pkg_fuse_repo = https://github.com/jlouis/fuse
pkg_fuse_commit = master
PACKAGES += gcm
pkg_gcm_name = gcm
pkg_gcm_description = An Erlang application for Google Cloud Messaging
pkg_gcm_homepage = https://github.com/pdincau/gcm-erlang
pkg_gcm_fetch = git
pkg_gcm_repo = https://github.com/pdincau/gcm-erlang
pkg_gcm_commit = master
PACKAGES += gcprof
pkg_gcprof_name = gcprof
pkg_gcprof_description = Garbage Collection profiler for Erlang
pkg_gcprof_homepage = https://github.com/knutin/gcprof
pkg_gcprof_fetch = git
pkg_gcprof_repo = https://github.com/knutin/gcprof
pkg_gcprof_commit = master
PACKAGES += geas
pkg_geas_name = geas
pkg_geas_description = Guess Erlang Application Scattering
pkg_geas_homepage = https://github.com/crownedgrouse/geas
pkg_geas_fetch = git
pkg_geas_repo = https://github.com/crownedgrouse/geas
pkg_geas_commit = master
PACKAGES += geef
pkg_geef_name = geef
pkg_geef_description = Git NEEEEF (Erlang NIF)
pkg_geef_homepage = https://github.com/carlosmn/geef
pkg_geef_fetch = git
pkg_geef_repo = https://github.com/carlosmn/geef
pkg_geef_commit = master
PACKAGES += gen_cycle
pkg_gen_cycle_name = gen_cycle
pkg_gen_cycle_description = Simple, generic OTP behaviour for recurring tasks
pkg_gen_cycle_homepage = https://github.com/aerosol/gen_cycle
pkg_gen_cycle_fetch = git
pkg_gen_cycle_repo = https://github.com/aerosol/gen_cycle
pkg_gen_cycle_commit = develop
PACKAGES += gen_icmp
pkg_gen_icmp_name = gen_icmp
pkg_gen_icmp_description = Erlang interface to ICMP sockets
pkg_gen_icmp_homepage = https://github.com/msantos/gen_icmp
pkg_gen_icmp_fetch = git
pkg_gen_icmp_repo = https://github.com/msantos/gen_icmp
pkg_gen_icmp_commit = master
PACKAGES += gen_nb_server
pkg_gen_nb_server_name = gen_nb_server
pkg_gen_nb_server_description = OTP behavior for writing non-blocking servers
pkg_gen_nb_server_homepage = https://github.com/kevsmith/gen_nb_server
pkg_gen_nb_server_fetch = git
pkg_gen_nb_server_repo = https://github.com/kevsmith/gen_nb_server
pkg_gen_nb_server_commit = master
PACKAGES += gen_paxos
pkg_gen_paxos_name = gen_paxos
pkg_gen_paxos_description = An Erlang/OTP-style implementation of the PAXOS distributed consensus protocol
pkg_gen_paxos_homepage = https://github.com/gburd/gen_paxos
pkg_gen_paxos_fetch = git
pkg_gen_paxos_repo = https://github.com/gburd/gen_paxos
pkg_gen_paxos_commit = master
PACKAGES += gen_smtp
pkg_gen_smtp_name = gen_smtp
pkg_gen_smtp_description = A generic Erlang SMTP server and client that can be extended via callback modules
pkg_gen_smtp_homepage = https://github.com/Vagabond/gen_smtp
pkg_gen_smtp_fetch = git
pkg_gen_smtp_repo = https://github.com/Vagabond/gen_smtp
pkg_gen_smtp_commit = master
PACKAGES += gen_tracker
pkg_gen_tracker_name = gen_tracker
pkg_gen_tracker_description = supervisor with ets handling of children and their metadata
pkg_gen_tracker_homepage = https://github.com/erlyvideo/gen_tracker
pkg_gen_tracker_fetch = git
pkg_gen_tracker_repo = https://github.com/erlyvideo/gen_tracker
pkg_gen_tracker_commit = master
PACKAGES += gen_unix
pkg_gen_unix_name = gen_unix
pkg_gen_unix_description = Erlang Unix socket interface
pkg_gen_unix_homepage = https://github.com/msantos/gen_unix
pkg_gen_unix_fetch = git
pkg_gen_unix_repo = https://github.com/msantos/gen_unix
pkg_gen_unix_commit = master
PACKAGES += getopt
pkg_getopt_name = getopt
pkg_getopt_description = Module to parse command line arguments using the GNU getopt syntax
pkg_getopt_homepage = https://github.com/jcomellas/getopt
pkg_getopt_fetch = git
pkg_getopt_repo = https://github.com/jcomellas/getopt
pkg_getopt_commit = master
PACKAGES += gettext
pkg_gettext_name = gettext
pkg_gettext_description = Erlang internationalization library.
pkg_gettext_homepage = https://github.com/etnt/gettext
pkg_gettext_fetch = git
pkg_gettext_repo = https://github.com/etnt/gettext
pkg_gettext_commit = master
PACKAGES += giallo
pkg_giallo_name = giallo
pkg_giallo_description = Small and flexible web framework on top of Cowboy
pkg_giallo_homepage = https://github.com/kivra/giallo
pkg_giallo_fetch = git
pkg_giallo_repo = https://github.com/kivra/giallo
pkg_giallo_commit = master
PACKAGES += gin
pkg_gin_name = gin
pkg_gin_description = The guards and for Erlang parse_transform
pkg_gin_homepage = https://github.com/mad-cocktail/gin
pkg_gin_fetch = git
pkg_gin_repo = https://github.com/mad-cocktail/gin
pkg_gin_commit = master
PACKAGES += gitty
pkg_gitty_name = gitty
pkg_gitty_description = Git access in erlang
pkg_gitty_homepage = https://github.com/maxlapshin/gitty
pkg_gitty_fetch = git
pkg_gitty_repo = https://github.com/maxlapshin/gitty
pkg_gitty_commit = master
PACKAGES += gold_fever
pkg_gold_fever_name = gold_fever
pkg_gold_fever_description = A Treasure Hunt for Erlangers
pkg_gold_fever_homepage = https://github.com/inaka/gold_fever
pkg_gold_fever_fetch = git
pkg_gold_fever_repo = https://github.com/inaka/gold_fever
pkg_gold_fever_commit = master
PACKAGES += gossiperl
pkg_gossiperl_name = gossiperl
pkg_gossiperl_description = Gossip middleware in Erlang
pkg_gossiperl_homepage = http://gossiperl.com/
pkg_gossiperl_fetch = git
pkg_gossiperl_repo = https://github.com/gossiperl/gossiperl
pkg_gossiperl_commit = master
PACKAGES += gpb
pkg_gpb_name = gpb
pkg_gpb_description = A Google Protobuf implementation for Erlang
pkg_gpb_homepage = https://github.com/tomas-abrahamsson/gpb
pkg_gpb_fetch = git
pkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb
pkg_gpb_commit = master
PACKAGES += gproc
pkg_gproc_name = gproc
pkg_gproc_description = Extended process registry for Erlang
pkg_gproc_homepage = https://github.com/uwiger/gproc
pkg_gproc_fetch = git
pkg_gproc_repo = https://github.com/uwiger/gproc
pkg_gproc_commit = master
PACKAGES += grapherl
pkg_grapherl_name = grapherl
pkg_grapherl_description = Create graphs of Erlang systems and programs
pkg_grapherl_homepage = https://github.com/eproxus/grapherl
pkg_grapherl_fetch = git
pkg_grapherl_repo = https://github.com/eproxus/grapherl
pkg_grapherl_commit = master
PACKAGES += gun
pkg_gun_name = gun
pkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang.
pkg_gun_homepage = http//ninenines.eu
pkg_gun_fetch = git
pkg_gun_repo = https://github.com/ninenines/gun
pkg_gun_commit = master
PACKAGES += gut
pkg_gut_name = gut
pkg_gut_description = gut is a template printing, aka scaffolding, tool for Erlang. Like rails generate or yeoman
pkg_gut_homepage = https://github.com/unbalancedparentheses/gut
pkg_gut_fetch = git
pkg_gut_repo = https://github.com/unbalancedparentheses/gut
pkg_gut_commit = master
PACKAGES += hackney
pkg_hackney_name = hackney
pkg_hackney_description = simple HTTP client in Erlang
pkg_hackney_homepage = https://github.com/benoitc/hackney
pkg_hackney_fetch = git
pkg_hackney_repo = https://github.com/benoitc/hackney
pkg_hackney_commit = master
PACKAGES += hamcrest
pkg_hamcrest_name = hamcrest
pkg_hamcrest_description = Erlang port of Hamcrest
pkg_hamcrest_homepage = https://github.com/hyperthunk/hamcrest-erlang
pkg_hamcrest_fetch = git
pkg_hamcrest_repo = https://github.com/hyperthunk/hamcrest-erlang
pkg_hamcrest_commit = master
PACKAGES += hanoidb
pkg_hanoidb_name = hanoidb
pkg_hanoidb_description = Erlang LSM BTree Storage
pkg_hanoidb_homepage = https://github.com/krestenkrab/hanoidb
pkg_hanoidb_fetch = git
pkg_hanoidb_repo = https://github.com/krestenkrab/hanoidb
pkg_hanoidb_commit = master
PACKAGES += hottub
pkg_hottub_name = hottub
pkg_hottub_description = Permanent Erlang Worker Pool
pkg_hottub_homepage = https://github.com/bfrog/hottub
pkg_hottub_fetch = git
pkg_hottub_repo = https://github.com/bfrog/hottub
pkg_hottub_commit = master
PACKAGES += hpack
pkg_hpack_name = hpack
pkg_hpack_description = HPACK Implementation for Erlang
pkg_hpack_homepage = https://github.com/joedevivo/hpack
pkg_hpack_fetch = git
pkg_hpack_repo = https://github.com/joedevivo/hpack
pkg_hpack_commit = master
PACKAGES += hyper
pkg_hyper_name = hyper
pkg_hyper_description = Erlang implementation of HyperLogLog
pkg_hyper_homepage = https://github.com/GameAnalytics/hyper
pkg_hyper_fetch = git
pkg_hyper_repo = https://github.com/GameAnalytics/hyper
pkg_hyper_commit = master
PACKAGES += ibrowse
pkg_ibrowse_name = ibrowse
pkg_ibrowse_description = Erlang HTTP client
pkg_ibrowse_homepage = https://github.com/cmullaparthi/ibrowse
pkg_ibrowse_fetch = git
pkg_ibrowse_repo = https://github.com/cmullaparthi/ibrowse
pkg_ibrowse_commit = v4.1.1
PACKAGES += ierlang
pkg_ierlang_name = ierlang
pkg_ierlang_description = An Erlang language kernel for IPython.
pkg_ierlang_homepage = https://github.com/robbielynch/ierlang
pkg_ierlang_fetch = git
pkg_ierlang_repo = https://github.com/robbielynch/ierlang
pkg_ierlang_commit = master
PACKAGES += iota
pkg_iota_name = iota
pkg_iota_description = iota (Inter-dependency Objective Testing Apparatus) - a tool to enforce clean separation of responsibilities in Erlang code
pkg_iota_homepage = https://github.com/jpgneves/iota
pkg_iota_fetch = git
pkg_iota_repo = https://github.com/jpgneves/iota
pkg_iota_commit = master
PACKAGES += ircd
pkg_ircd_name = ircd
pkg_ircd_description = A pluggable IRC daemon application/library for Erlang.
pkg_ircd_homepage = https://github.com/tonyg/erlang-ircd
pkg_ircd_fetch = git
pkg_ircd_repo = https://github.com/tonyg/erlang-ircd
pkg_ircd_commit = master
PACKAGES += irc_lib
pkg_irc_lib_name = irc_lib
pkg_irc_lib_description = Erlang irc client library
pkg_irc_lib_homepage = https://github.com/OtpChatBot/irc_lib
pkg_irc_lib_fetch = git
pkg_irc_lib_repo = https://github.com/OtpChatBot/irc_lib
pkg_irc_lib_commit = master
PACKAGES += iris
pkg_iris_name = iris
pkg_iris_description = Iris Erlang binding
pkg_iris_homepage = https://github.com/project-iris/iris-erl
pkg_iris_fetch = git
pkg_iris_repo = https://github.com/project-iris/iris-erl
pkg_iris_commit = master
PACKAGES += iso8601
pkg_iso8601_name = iso8601
pkg_iso8601_description = Erlang ISO 8601 date formatter/parser
pkg_iso8601_homepage = https://github.com/seansawyer/erlang_iso8601
pkg_iso8601_fetch = git
pkg_iso8601_repo = https://github.com/seansawyer/erlang_iso8601
pkg_iso8601_commit = master
PACKAGES += itweet
pkg_itweet_name = itweet
pkg_itweet_description = Twitter Stream API on ibrowse
pkg_itweet_homepage = http://inaka.github.com/itweet/
pkg_itweet_fetch = git
pkg_itweet_repo = https://github.com/inaka/itweet
pkg_itweet_commit = v2.0
PACKAGES += jerg
pkg_jerg_name = jerg
pkg_jerg_description = JSON Schema to Erlang Records Generator
pkg_jerg_homepage = https://github.com/ddossot/jerg
pkg_jerg_fetch = git
pkg_jerg_repo = https://github.com/ddossot/jerg
pkg_jerg_commit = master
PACKAGES += jesse
pkg_jesse_name = jesse
pkg_jesse_description = jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang.
pkg_jesse_homepage = https://github.com/klarna/jesse
pkg_jesse_fetch = git
pkg_jesse_repo = https://github.com/klarna/jesse
pkg_jesse_commit = master
PACKAGES += jiffy
pkg_jiffy_name = jiffy
pkg_jiffy_description = JSON NIFs for Erlang.
pkg_jiffy_homepage = https://github.com/davisp/jiffy
pkg_jiffy_fetch = git
pkg_jiffy_repo = https://github.com/davisp/jiffy
pkg_jiffy_commit = master
PACKAGES += jiffy_v
pkg_jiffy_v_name = jiffy_v
pkg_jiffy_v_description = JSON validation utility
pkg_jiffy_v_homepage = https://github.com/shizzard/jiffy-v
pkg_jiffy_v_fetch = git
pkg_jiffy_v_repo = https://github.com/shizzard/jiffy-v
pkg_jiffy_v_commit = 0.3.3
PACKAGES += jobs
pkg_jobs_name = jobs
pkg_jobs_description = a Job scheduler for load regulation
pkg_jobs_homepage = https://github.com/esl/jobs
pkg_jobs_fetch = git
pkg_jobs_repo = https://github.com/esl/jobs
pkg_jobs_commit = 0.3
PACKAGES += joxa
pkg_joxa_name = joxa
pkg_joxa_description = A Modern Lisp for the Erlang VM
pkg_joxa_homepage = https://github.com/joxa/joxa
pkg_joxa_fetch = git
pkg_joxa_repo = https://github.com/joxa/joxa
pkg_joxa_commit = master
PACKAGES += jsonerl
pkg_jsonerl_name = jsonerl
pkg_jsonerl_description = yet another but slightly different erlang <-> json encoder/decoder
pkg_jsonerl_homepage = https://github.com/lambder/jsonerl
pkg_jsonerl_fetch = git
pkg_jsonerl_repo = https://github.com/lambder/jsonerl
pkg_jsonerl_commit = master
PACKAGES += json
pkg_json_name = json
pkg_json_description = a high level json library for erlang (17.0+)
pkg_json_homepage = https://github.com/talentdeficit/json
pkg_json_fetch = git
pkg_json_repo = https://github.com/talentdeficit/json
pkg_json_commit = master
PACKAGES += jsonpath
pkg_jsonpath_name = jsonpath
pkg_jsonpath_description = Fast Erlang JSON data retrieval and updates via javascript-like notation
pkg_jsonpath_homepage = https://github.com/GeneStevens/jsonpath
pkg_jsonpath_fetch = git
pkg_jsonpath_repo = https://github.com/GeneStevens/jsonpath
pkg_jsonpath_commit = master
PACKAGES += json_rec
pkg_json_rec_name = json_rec
pkg_json_rec_description = JSON to erlang record
pkg_json_rec_homepage = https://github.com/justinkirby/json_rec
pkg_json_rec_fetch = git
pkg_json_rec_repo = https://github.com/justinkirby/json_rec
pkg_json_rec_commit = master
PACKAGES += jsonx
pkg_jsonx_name = jsonx
pkg_jsonx_description = JSONX is an Erlang library for efficient decode and encode JSON, written in C.
pkg_jsonx_homepage = https://github.com/iskra/jsonx
pkg_jsonx_fetch = git
pkg_jsonx_repo = https://github.com/iskra/jsonx
pkg_jsonx_commit = master
PACKAGES += jsx
pkg_jsx_name = jsx
pkg_jsx_description = An Erlang application for consuming, producing and manipulating JSON.
pkg_jsx_homepage = https://github.com/talentdeficit/jsx
pkg_jsx_fetch = git
pkg_jsx_repo = https://github.com/talentdeficit/jsx
pkg_jsx_commit = master
PACKAGES += kafka
pkg_kafka_name = kafka
pkg_kafka_description = Kafka consumer and producer in Erlang
pkg_kafka_homepage = https://github.com/wooga/kafka-erlang
pkg_kafka_fetch = git
pkg_kafka_repo = https://github.com/wooga/kafka-erlang
pkg_kafka_commit = master
PACKAGES += kai
pkg_kai_name = kai
pkg_kai_description = DHT storage by Takeshi Inoue
pkg_kai_homepage = https://github.com/synrc/kai
pkg_kai_fetch = git
pkg_kai_repo = https://github.com/synrc/kai
pkg_kai_commit = master
PACKAGES += katja
pkg_katja_name = katja
pkg_katja_description = A simple Riemann client written in Erlang.
pkg_katja_homepage = https://github.com/nifoc/katja
pkg_katja_fetch = git
pkg_katja_repo = https://github.com/nifoc/katja
pkg_katja_commit = master
PACKAGES += kdht
pkg_kdht_name = kdht
pkg_kdht_description = kdht is an erlang DHT implementation
pkg_kdht_homepage = https://github.com/kevinlynx/kdht
pkg_kdht_fetch = git
pkg_kdht_repo = https://github.com/kevinlynx/kdht
pkg_kdht_commit = master
PACKAGES += key2value
pkg_key2value_name = key2value
pkg_key2value_description = Erlang 2-way map
pkg_key2value_homepage = https://github.com/okeuday/key2value
pkg_key2value_fetch = git
pkg_key2value_repo = https://github.com/okeuday/key2value
pkg_key2value_commit = master
PACKAGES += keys1value
pkg_keys1value_name = keys1value
pkg_keys1value_description = Erlang set associative map for key lists
pkg_keys1value_homepage = https://github.com/okeuday/keys1value
pkg_keys1value_fetch = git
pkg_keys1value_repo = https://github.com/okeuday/keys1value
pkg_keys1value_commit = master
PACKAGES += kinetic
pkg_kinetic_name = kinetic
pkg_kinetic_description = Erlang Kinesis Client
pkg_kinetic_homepage = https://github.com/AdRoll/kinetic
pkg_kinetic_fetch = git
pkg_kinetic_repo = https://github.com/AdRoll/kinetic
pkg_kinetic_commit = master
PACKAGES += kjell
pkg_kjell_name = kjell
pkg_kjell_description = Erlang Shell
pkg_kjell_homepage = https://github.com/karlll/kjell
pkg_kjell_fetch = git
pkg_kjell_repo = https://github.com/karlll/kjell
pkg_kjell_commit = master
PACKAGES += kraken
pkg_kraken_name = kraken
pkg_kraken_description = Distributed Pubsub Server for Realtime Apps
pkg_kraken_homepage = https://github.com/Asana/kraken
pkg_kraken_fetch = git
pkg_kraken_repo = https://github.com/Asana/kraken
pkg_kraken_commit = master
PACKAGES += kucumberl
pkg_kucumberl_name = kucumberl
pkg_kucumberl_description = A pure-erlang, open-source, implementation of Cucumber
pkg_kucumberl_homepage = https://github.com/openshine/kucumberl
pkg_kucumberl_fetch = git
pkg_kucumberl_repo = https://github.com/openshine/kucumberl
pkg_kucumberl_commit = master
PACKAGES += kvc
pkg_kvc_name = kvc
pkg_kvc_description = KVC - Key Value Coding for Erlang data structures
pkg_kvc_homepage = https://github.com/etrepum/kvc
pkg_kvc_fetch = git
pkg_kvc_repo = https://github.com/etrepum/kvc
pkg_kvc_commit = master
PACKAGES += kvlists
pkg_kvlists_name = kvlists
pkg_kvlists_description = Lists of key-value pairs (decoded JSON) in Erlang
pkg_kvlists_homepage = https://github.com/jcomellas/kvlists
pkg_kvlists_fetch = git
pkg_kvlists_repo = https://github.com/jcomellas/kvlists
pkg_kvlists_commit = master
PACKAGES += kvs
pkg_kvs_name = kvs
pkg_kvs_description = Container and Iterator
pkg_kvs_homepage = https://github.com/synrc/kvs
pkg_kvs_fetch = git
pkg_kvs_repo = https://github.com/synrc/kvs
pkg_kvs_commit = master
PACKAGES += lager_amqp_backend
pkg_lager_amqp_backend_name = lager_amqp_backend
pkg_lager_amqp_backend_description = AMQP RabbitMQ Lager backend
pkg_lager_amqp_backend_homepage = https://github.com/jbrisbin/lager_amqp_backend
pkg_lager_amqp_backend_fetch = git
pkg_lager_amqp_backend_repo = https://github.com/jbrisbin/lager_amqp_backend
pkg_lager_amqp_backend_commit = master
PACKAGES += lager
pkg_lager_name = lager
pkg_lager_description = A logging framework for Erlang/OTP.
pkg_lager_homepage = https://github.com/basho/lager
pkg_lager_fetch = git
pkg_lager_repo = https://github.com/basho/lager
pkg_lager_commit = master
PACKAGES += lager_syslog
pkg_lager_syslog_name = lager_syslog
pkg_lager_syslog_description = Syslog backend for lager
pkg_lager_syslog_homepage = https://github.com/basho/lager_syslog
pkg_lager_syslog_fetch = git
pkg_lager_syslog_repo = https://github.com/basho/lager_syslog
pkg_lager_syslog_commit = master
PACKAGES += lambdapad
pkg_lambdapad_name = lambdapad
pkg_lambdapad_description = Static site generator using Erlang. Yes, Erlang.
pkg_lambdapad_homepage = https://github.com/gar1t/lambdapad
pkg_lambdapad_fetch = git
pkg_lambdapad_repo = https://github.com/gar1t/lambdapad
pkg_lambdapad_commit = master
PACKAGES += lasp
pkg_lasp_name = lasp
pkg_lasp_description = A Language for Distributed, Eventually Consistent Computations
pkg_lasp_homepage = http://lasp-lang.org/
pkg_lasp_fetch = git
pkg_lasp_repo = https://github.com/lasp-lang/lasp
pkg_lasp_commit = master
PACKAGES += lasse
pkg_lasse_name = lasse
pkg_lasse_description = SSE handler for Cowboy
pkg_lasse_homepage = https://github.com/inaka/lasse
pkg_lasse_fetch = git
pkg_lasse_repo = https://github.com/inaka/lasse
pkg_lasse_commit = 0.1.0
PACKAGES += ldap
pkg_ldap_name = ldap
pkg_ldap_description = LDAP server written in Erlang
pkg_ldap_homepage = https://github.com/spawnproc/ldap
pkg_ldap_fetch = git
pkg_ldap_repo = https://github.com/spawnproc/ldap
pkg_ldap_commit = master
PACKAGES += lethink
pkg_lethink_name = lethink
pkg_lethink_description = erlang driver for rethinkdb
pkg_lethink_homepage = https://github.com/taybin/lethink
pkg_lethink_fetch = git
pkg_lethink_repo = https://github.com/taybin/lethink
pkg_lethink_commit = master
PACKAGES += lfe
pkg_lfe_name = lfe
pkg_lfe_description = Lisp Flavoured Erlang (LFE)
pkg_lfe_homepage = https://github.com/rvirding/lfe
pkg_lfe_fetch = git
pkg_lfe_repo = https://github.com/rvirding/lfe
pkg_lfe_commit = master
PACKAGES += ling
pkg_ling_name = ling
pkg_ling_description = Erlang on Xen
pkg_ling_homepage = https://github.com/cloudozer/ling
pkg_ling_fetch = git
pkg_ling_repo = https://github.com/cloudozer/ling
pkg_ling_commit = master
PACKAGES += live
pkg_live_name = live
pkg_live_description = Automated module and configuration reloader.
pkg_live_homepage = http://ninenines.eu
pkg_live_fetch = git
pkg_live_repo = https://github.com/ninenines/live
pkg_live_commit = master
PACKAGES += lmq
pkg_lmq_name = lmq
pkg_lmq_description = Lightweight Message Queue
pkg_lmq_homepage = https://github.com/iij/lmq
pkg_lmq_fetch = git
pkg_lmq_repo = https://github.com/iij/lmq
pkg_lmq_commit = master
PACKAGES += locker
pkg_locker_name = locker
pkg_locker_description = Atomic distributed 'check and set' for short-lived keys
pkg_locker_homepage = https://github.com/wooga/locker
pkg_locker_fetch = git
pkg_locker_repo = https://github.com/wooga/locker
pkg_locker_commit = master
PACKAGES += locks
pkg_locks_name = locks
pkg_locks_description = A scalable, deadlock-resolving resource locker
pkg_locks_homepage = https://github.com/uwiger/locks
pkg_locks_fetch = git
pkg_locks_repo = https://github.com/uwiger/locks
pkg_locks_commit = master
PACKAGES += log4erl
pkg_log4erl_name = log4erl
pkg_log4erl_description = A logger for erlang in the spirit of Log4J.
pkg_log4erl_homepage = https://github.com/ahmednawras/log4erl
pkg_log4erl_fetch = git
pkg_log4erl_repo = https://github.com/ahmednawras/log4erl
pkg_log4erl_commit = master
PACKAGES += lol
pkg_lol_name = lol
pkg_lol_description = Lisp on erLang, and programming is fun again
pkg_lol_homepage = https://github.com/b0oh/lol
pkg_lol_fetch = git
pkg_lol_repo = https://github.com/b0oh/lol
pkg_lol_commit = master
PACKAGES += lucid
pkg_lucid_name = lucid
pkg_lucid_description = HTTP/2 server written in Erlang
pkg_lucid_homepage = https://github.com/tatsuhiro-t/lucid
pkg_lucid_fetch = git
pkg_lucid_repo = https://github.com/tatsuhiro-t/lucid
pkg_lucid_commit = master
PACKAGES += luerl
pkg_luerl_name = luerl
pkg_luerl_description = Lua in Erlang
pkg_luerl_homepage = https://github.com/rvirding/luerl
pkg_luerl_fetch = git
pkg_luerl_repo = https://github.com/rvirding/luerl
pkg_luerl_commit = develop
PACKAGES += luwak
pkg_luwak_name = luwak
pkg_luwak_description = Large-object storage interface for Riak
pkg_luwak_homepage = https://github.com/basho/luwak
pkg_luwak_fetch = git
pkg_luwak_repo = https://github.com/basho/luwak
pkg_luwak_commit = master
PACKAGES += lux
pkg_lux_name = lux
pkg_lux_description = Lux (LUcid eXpect scripting) simplifies test automation and provides an Expect-style execution of commands
pkg_lux_homepage = https://github.com/hawk/lux
pkg_lux_fetch = git
pkg_lux_repo = https://github.com/hawk/lux
pkg_lux_commit = master
PACKAGES += machi
pkg_machi_name = machi
pkg_machi_description = Machi file store
pkg_machi_homepage = https://github.com/basho/machi
pkg_machi_fetch = git
pkg_machi_repo = https://github.com/basho/machi
pkg_machi_commit = master
PACKAGES += mad
pkg_mad_name = mad
pkg_mad_description = Small and Fast Rebar Replacement
pkg_mad_homepage = https://github.com/synrc/mad
pkg_mad_fetch = git
pkg_mad_repo = https://github.com/synrc/mad
pkg_mad_commit = master
PACKAGES += marina
pkg_marina_name = marina
pkg_marina_description = Non-blocking Erlang Cassandra CQL3 client
pkg_marina_homepage = https://github.com/lpgauth/marina
pkg_marina_fetch = git
pkg_marina_repo = https://github.com/lpgauth/marina
pkg_marina_commit = master
PACKAGES += mavg
pkg_mavg_name = mavg
pkg_mavg_description = Erlang :: Exponential moving average library
pkg_mavg_homepage = https://github.com/EchoTeam/mavg
pkg_mavg_fetch = git
pkg_mavg_repo = https://github.com/EchoTeam/mavg
pkg_mavg_commit = master
PACKAGES += mcd
pkg_mcd_name = mcd
pkg_mcd_description = Fast memcached protocol client in pure Erlang
pkg_mcd_homepage = https://github.com/EchoTeam/mcd
pkg_mcd_fetch = git
pkg_mcd_repo = https://github.com/EchoTeam/mcd
pkg_mcd_commit = master
PACKAGES += mcerlang
pkg_mcerlang_name = mcerlang
pkg_mcerlang_description = The McErlang model checker for Erlang
pkg_mcerlang_homepage = https://github.com/fredlund/McErlang
pkg_mcerlang_fetch = git
pkg_mcerlang_repo = https://github.com/fredlund/McErlang
pkg_mcerlang_commit = master
PACKAGES += mc_erl
pkg_mc_erl_name = mc_erl
pkg_mc_erl_description = mc-erl is a server for Minecraft 1.4.7 written in Erlang.
pkg_mc_erl_homepage = https://github.com/clonejo/mc-erl
pkg_mc_erl_fetch = git
pkg_mc_erl_repo = https://github.com/clonejo/mc-erl
pkg_mc_erl_commit = master
PACKAGES += meck
pkg_meck_name = meck
pkg_meck_description = A mocking library for Erlang
pkg_meck_homepage = https://github.com/eproxus/meck
pkg_meck_fetch = git
pkg_meck_repo = https://github.com/eproxus/meck
pkg_meck_commit = master
PACKAGES += mekao
pkg_mekao_name = mekao
pkg_mekao_description = SQL constructor
pkg_mekao_homepage = https://github.com/ddosia/mekao
pkg_mekao_fetch = git
pkg_mekao_repo = https://github.com/ddosia/mekao
pkg_mekao_commit = master
PACKAGES += memo
pkg_memo_name = memo
pkg_memo_description = Erlang memoization server
pkg_memo_homepage = https://github.com/tuncer/memo
pkg_memo_fetch = git
pkg_memo_repo = https://github.com/tuncer/memo
pkg_memo_commit = master
PACKAGES += merge_index
pkg_merge_index_name = merge_index
pkg_merge_index_description = MergeIndex is an Erlang library for storing ordered sets on disk. It is very similar to an SSTable (in Google's Bigtable) or an HFile (in Hadoop).
pkg_merge_index_homepage = https://github.com/basho/merge_index
pkg_merge_index_fetch = git
pkg_merge_index_repo = https://github.com/basho/merge_index
pkg_merge_index_commit = master
PACKAGES += merl
pkg_merl_name = merl
pkg_merl_description = Metaprogramming in Erlang
pkg_merl_homepage = https://github.com/richcarl/merl
pkg_merl_fetch = git
pkg_merl_repo = https://github.com/richcarl/merl
pkg_merl_commit = master
PACKAGES += mimetypes
pkg_mimetypes_name = mimetypes
pkg_mimetypes_description = Erlang MIME types library
pkg_mimetypes_homepage = https://github.com/spawngrid/mimetypes
pkg_mimetypes_fetch = git
pkg_mimetypes_repo = https://github.com/spawngrid/mimetypes
pkg_mimetypes_commit = master
PACKAGES += mixer
pkg_mixer_name = mixer
pkg_mixer_description = Mix in functions from other modules
pkg_mixer_homepage = https://github.com/chef/mixer
pkg_mixer_fetch = git
pkg_mixer_repo = https://github.com/chef/mixer
pkg_mixer_commit = master
PACKAGES += mochiweb
pkg_mochiweb_name = mochiweb
pkg_mochiweb_description = MochiWeb is an Erlang library for building lightweight HTTP servers.
pkg_mochiweb_homepage = https://github.com/mochi/mochiweb
pkg_mochiweb_fetch = git
pkg_mochiweb_repo = https://github.com/mochi/mochiweb
pkg_mochiweb_commit = master
PACKAGES += mochiweb_xpath
pkg_mochiweb_xpath_name = mochiweb_xpath
pkg_mochiweb_xpath_description = XPath support for mochiweb's html parser
pkg_mochiweb_xpath_homepage = https://github.com/retnuh/mochiweb_xpath
pkg_mochiweb_xpath_fetch = git
pkg_mochiweb_xpath_repo = https://github.com/retnuh/mochiweb_xpath
pkg_mochiweb_xpath_commit = master
PACKAGES += mockgyver
pkg_mockgyver_name = mockgyver
pkg_mockgyver_description = A mocking library for Erlang
pkg_mockgyver_homepage = https://github.com/klajo/mockgyver
pkg_mockgyver_fetch = git
pkg_mockgyver_repo = https://github.com/klajo/mockgyver
pkg_mockgyver_commit = master
PACKAGES += modlib
pkg_modlib_name = modlib
pkg_modlib_description = Web framework based on Erlang's inets httpd
pkg_modlib_homepage = https://github.com/gar1t/modlib
pkg_modlib_fetch = git
pkg_modlib_repo = https://github.com/gar1t/modlib
pkg_modlib_commit = master
PACKAGES += mongodb
pkg_mongodb_name = mongodb
pkg_mongodb_description = MongoDB driver for Erlang
pkg_mongodb_homepage = https://github.com/comtihon/mongodb-erlang
pkg_mongodb_fetch = git
pkg_mongodb_repo = https://github.com/comtihon/mongodb-erlang
pkg_mongodb_commit = master
PACKAGES += mongooseim
pkg_mongooseim_name = mongooseim
pkg_mongooseim_description = Jabber / XMPP server with focus on performance and scalability, by Erlang Solutions
pkg_mongooseim_homepage = https://www.erlang-solutions.com/products/mongooseim-massively-scalable-ejabberd-platform
pkg_mongooseim_fetch = git
pkg_mongooseim_repo = https://github.com/esl/MongooseIM
pkg_mongooseim_commit = master
PACKAGES += moyo
pkg_moyo_name = moyo
pkg_moyo_description = Erlang utility functions library
pkg_moyo_homepage = https://github.com/dwango/moyo
pkg_moyo_fetch = git
pkg_moyo_repo = https://github.com/dwango/moyo
pkg_moyo_commit = master
PACKAGES += msgpack
pkg_msgpack_name = msgpack
pkg_msgpack_description = MessagePack (de)serializer implementation for Erlang
pkg_msgpack_homepage = https://github.com/msgpack/msgpack-erlang
pkg_msgpack_fetch = git
pkg_msgpack_repo = https://github.com/msgpack/msgpack-erlang
pkg_msgpack_commit = master
PACKAGES += mu2
pkg_mu2_name = mu2
pkg_mu2_description = Erlang mutation testing tool
pkg_mu2_homepage = https://github.com/ramsay-t/mu2
pkg_mu2_fetch = git
pkg_mu2_repo = https://github.com/ramsay-t/mu2
pkg_mu2_commit = master
PACKAGES += mustache
pkg_mustache_name = mustache
pkg_mustache_description = Mustache template engine for Erlang.
pkg_mustache_homepage = https://github.com/mojombo/mustache.erl
pkg_mustache_fetch = git
pkg_mustache_repo = https://github.com/mojombo/mustache.erl
pkg_mustache_commit = master
PACKAGES += myproto
pkg_myproto_name = myproto
pkg_myproto_description = MySQL Server Protocol in Erlang
pkg_myproto_homepage = https://github.com/altenwald/myproto
pkg_myproto_fetch = git
pkg_myproto_repo = https://github.com/altenwald/myproto
pkg_myproto_commit = master
PACKAGES += mysql
pkg_mysql_name = mysql
pkg_mysql_description = Erlang MySQL Driver (from code.google.com)
pkg_mysql_homepage = https://github.com/dizzyd/erlang-mysql-driver
pkg_mysql_fetch = git
pkg_mysql_repo = https://github.com/dizzyd/erlang-mysql-driver
pkg_mysql_commit = master
PACKAGES += n2o
pkg_n2o_name = n2o
pkg_n2o_description = WebSocket Application Server
pkg_n2o_homepage = https://github.com/5HT/n2o
pkg_n2o_fetch = git
pkg_n2o_repo = https://github.com/5HT/n2o
pkg_n2o_commit = master
PACKAGES += nat_upnp
pkg_nat_upnp_name = nat_upnp
pkg_nat_upnp_description = Erlang library to map your internal port to an external using UNP IGD
pkg_nat_upnp_homepage = https://github.com/benoitc/nat_upnp
pkg_nat_upnp_fetch = git
pkg_nat_upnp_repo = https://github.com/benoitc/nat_upnp
pkg_nat_upnp_commit = master
PACKAGES += neo4j
pkg_neo4j_name = neo4j
pkg_neo4j_description = Erlang client library for Neo4J.
pkg_neo4j_homepage = https://github.com/dmitriid/neo4j-erlang
pkg_neo4j_fetch = git
pkg_neo4j_repo = https://github.com/dmitriid/neo4j-erlang
pkg_neo4j_commit = master
PACKAGES += neotoma
pkg_neotoma_name = neotoma
pkg_neotoma_description = Erlang library and packrat parser-generator for parsing expression grammars.
pkg_neotoma_homepage = https://github.com/seancribbs/neotoma
pkg_neotoma_fetch = git
pkg_neotoma_repo = https://github.com/seancribbs/neotoma
pkg_neotoma_commit = master
PACKAGES += newrelic
pkg_newrelic_name = newrelic
pkg_newrelic_description = Erlang library for sending metrics to New Relic
pkg_newrelic_homepage = https://github.com/wooga/newrelic-erlang
pkg_newrelic_fetch = git
pkg_newrelic_repo = https://github.com/wooga/newrelic-erlang
pkg_newrelic_commit = master
PACKAGES += nifty
pkg_nifty_name = nifty
pkg_nifty_description = Erlang NIF wrapper generator
pkg_nifty_homepage = https://github.com/parapluu/nifty
pkg_nifty_fetch = git
pkg_nifty_repo = https://github.com/parapluu/nifty
pkg_nifty_commit = master
PACKAGES += nitrogen_core
pkg_nitrogen_core_name = nitrogen_core
pkg_nitrogen_core_description = The core Nitrogen library.
pkg_nitrogen_core_homepage = http://nitrogenproject.com/
pkg_nitrogen_core_fetch = git
pkg_nitrogen_core_repo = https://github.com/nitrogen/nitrogen_core
pkg_nitrogen_core_commit = master
PACKAGES += nkbase
pkg_nkbase_name = nkbase
pkg_nkbase_description = NkBASE distributed database
pkg_nkbase_homepage = https://github.com/Nekso/nkbase
pkg_nkbase_fetch = git
pkg_nkbase_repo = https://github.com/Nekso/nkbase
pkg_nkbase_commit = develop
PACKAGES += nkdocker
pkg_nkdocker_name = nkdocker
pkg_nkdocker_description = Erlang Docker client
pkg_nkdocker_homepage = https://github.com/Nekso/nkdocker
pkg_nkdocker_fetch = git
pkg_nkdocker_repo = https://github.com/Nekso/nkdocker
pkg_nkdocker_commit = master
PACKAGES += nkpacket
pkg_nkpacket_name = nkpacket
pkg_nkpacket_description = Generic Erlang transport layer
pkg_nkpacket_homepage = https://github.com/Nekso/nkpacket
pkg_nkpacket_fetch = git
pkg_nkpacket_repo = https://github.com/Nekso/nkpacket
pkg_nkpacket_commit = master
PACKAGES += nodefinder
pkg_nodefinder_name = nodefinder
pkg_nodefinder_description = automatic node discovery via UDP multicast
pkg_nodefinder_homepage = https://github.com/erlanger/nodefinder
pkg_nodefinder_fetch = git
pkg_nodefinder_repo = https://github.com/okeuday/nodefinder
pkg_nodefinder_commit = master
PACKAGES += nprocreg
pkg_nprocreg_name = nprocreg
pkg_nprocreg_description = Minimal Distributed Erlang Process Registry
pkg_nprocreg_homepage = http://nitrogenproject.com/
pkg_nprocreg_fetch = git
pkg_nprocreg_repo = https://github.com/nitrogen/nprocreg
pkg_nprocreg_commit = master
PACKAGES += oauth2c
pkg_oauth2c_name = oauth2c
pkg_oauth2c_description = Erlang OAuth2 Client
pkg_oauth2c_homepage = https://github.com/kivra/oauth2_client
pkg_oauth2c_fetch = git
pkg_oauth2c_repo = https://github.com/kivra/oauth2_client
pkg_oauth2c_commit = master
PACKAGES += oauth2
pkg_oauth2_name = oauth2
pkg_oauth2_description = Erlang Oauth2 implementation
pkg_oauth2_homepage = https://github.com/kivra/oauth2
pkg_oauth2_fetch = git
pkg_oauth2_repo = https://github.com/kivra/oauth2
pkg_oauth2_commit = master
PACKAGES += oauth
pkg_oauth_name = oauth
pkg_oauth_description = An Erlang OAuth 1.0 implementation
pkg_oauth_homepage = https://github.com/tim/erlang-oauth
pkg_oauth_fetch = git
pkg_oauth_repo = https://github.com/tim/erlang-oauth
pkg_oauth_commit = master
PACKAGES += of_protocol
pkg_of_protocol_name = of_protocol
pkg_of_protocol_description = OpenFlow Protocol Library for Erlang
pkg_of_protocol_homepage = https://github.com/FlowForwarding/of_protocol
pkg_of_protocol_fetch = git
pkg_of_protocol_repo = https://github.com/FlowForwarding/of_protocol
pkg_of_protocol_commit = master
PACKAGES += openflow
pkg_openflow_name = openflow
pkg_openflow_description = An OpenFlow controller written in pure erlang
pkg_openflow_homepage = https://github.com/renatoaguiar/erlang-openflow
pkg_openflow_fetch = git
pkg_openflow_repo = https://github.com/renatoaguiar/erlang-openflow
pkg_openflow_commit = master
PACKAGES += openid
pkg_openid_name = openid
pkg_openid_description = Erlang OpenID
pkg_openid_homepage = https://github.com/brendonh/erl_openid
pkg_openid_fetch = git
pkg_openid_repo = https://github.com/brendonh/erl_openid
pkg_openid_commit = master
PACKAGES += openpoker
pkg_openpoker_name = openpoker
pkg_openpoker_description = Genesis Texas hold'em Game Server
pkg_openpoker_homepage = https://github.com/hpyhacking/openpoker
pkg_openpoker_fetch = git
pkg_openpoker_repo = https://github.com/hpyhacking/openpoker
pkg_openpoker_commit = master
PACKAGES += pal
pkg_pal_name = pal
pkg_pal_description = Pragmatic Authentication Library
pkg_pal_homepage = https://github.com/manifest/pal
pkg_pal_fetch = git
pkg_pal_repo = https://github.com/manifest/pal
pkg_pal_commit = master
PACKAGES += parse_trans
pkg_parse_trans_name = parse_trans
pkg_parse_trans_description = Parse transform utilities for Erlang
pkg_parse_trans_homepage = https://github.com/uwiger/parse_trans
pkg_parse_trans_fetch = git
pkg_parse_trans_repo = https://github.com/uwiger/parse_trans
pkg_parse_trans_commit = master
PACKAGES += parsexml
pkg_parsexml_name = parsexml
pkg_parsexml_description = Simple DOM XML parser with convenient and very simple API
pkg_parsexml_homepage = https://github.com/maxlapshin/parsexml
pkg_parsexml_fetch = git
pkg_parsexml_repo = https://github.com/maxlapshin/parsexml
pkg_parsexml_commit = master
PACKAGES += pegjs
pkg_pegjs_name = pegjs
pkg_pegjs_description = An implementation of PEG.js grammar for Erlang.
pkg_pegjs_homepage = https://github.com/dmitriid/pegjs
pkg_pegjs_fetch = git
pkg_pegjs_repo = https://github.com/dmitriid/pegjs
pkg_pegjs_commit = 0.3
PACKAGES += percept2
pkg_percept2_name = percept2
pkg_percept2_description = Concurrent profiling tool for Erlang
pkg_percept2_homepage = https://github.com/huiqing/percept2
pkg_percept2_fetch = git
pkg_percept2_repo = https://github.com/huiqing/percept2
pkg_percept2_commit = master
PACKAGES += pgsql
pkg_pgsql_name = pgsql
pkg_pgsql_description = Erlang PostgreSQL driver
pkg_pgsql_homepage = https://github.com/semiocast/pgsql
pkg_pgsql_fetch = git
pkg_pgsql_repo = https://github.com/semiocast/pgsql
pkg_pgsql_commit = master
PACKAGES += pkgx
pkg_pkgx_name = pkgx
pkg_pkgx_description = Build .deb packages from Erlang releases
pkg_pkgx_homepage = https://github.com/arjan/pkgx
pkg_pkgx_fetch = git
pkg_pkgx_repo = https://github.com/arjan/pkgx
pkg_pkgx_commit = master
PACKAGES += pkt
pkg_pkt_name = pkt
pkg_pkt_description = Erlang network protocol library
pkg_pkt_homepage = https://github.com/msantos/pkt
pkg_pkt_fetch = git
pkg_pkt_repo = https://github.com/msantos/pkt
pkg_pkt_commit = master
PACKAGES += plain_fsm
pkg_plain_fsm_name = plain_fsm
pkg_plain_fsm_description = A behaviour/support library for writing plain Erlang FSMs.
pkg_plain_fsm_homepage = https://github.com/uwiger/plain_fsm
pkg_plain_fsm_fetch = git
pkg_plain_fsm_repo = https://github.com/uwiger/plain_fsm
pkg_plain_fsm_commit = master
PACKAGES += plumtree
pkg_plumtree_name = plumtree
pkg_plumtree_description = Epidemic Broadcast Trees
pkg_plumtree_homepage = https://github.com/helium/plumtree
pkg_plumtree_fetch = git
pkg_plumtree_repo = https://github.com/helium/plumtree
pkg_plumtree_commit = master
PACKAGES += pmod_transform
pkg_pmod_transform_name = pmod_transform
pkg_pmod_transform_description = Parse transform for parameterized modules
pkg_pmod_transform_homepage = https://github.com/erlang/pmod_transform
pkg_pmod_transform_fetch = git
pkg_pmod_transform_repo = https://github.com/erlang/pmod_transform
pkg_pmod_transform_commit = master
PACKAGES += pobox
pkg_pobox_name = pobox
pkg_pobox_description = External buffer processes to protect against mailbox overflow in Erlang
pkg_pobox_homepage = https://github.com/ferd/pobox
pkg_pobox_fetch = git
pkg_pobox_repo = https://github.com/ferd/pobox
pkg_pobox_commit = master
PACKAGES += ponos
pkg_ponos_name = ponos
pkg_ponos_description = ponos is a simple yet powerful load generator written in erlang
pkg_ponos_homepage = https://github.com/klarna/ponos
pkg_ponos_fetch = git
pkg_ponos_repo = https://github.com/klarna/ponos
pkg_ponos_commit = master
PACKAGES += poolboy
pkg_poolboy_name = poolboy
pkg_poolboy_description = A hunky Erlang worker pool factory
pkg_poolboy_homepage = https://github.com/devinus/poolboy
pkg_poolboy_fetch = git
pkg_poolboy_repo = https://github.com/devinus/poolboy
pkg_poolboy_commit = master
PACKAGES += pooler
pkg_pooler_name = pooler
pkg_pooler_description = An OTP Process Pool Application
pkg_pooler_homepage = https://github.com/seth/pooler
pkg_pooler_fetch = git
pkg_pooler_repo = https://github.com/seth/pooler
pkg_pooler_commit = master
PACKAGES += pqueue
pkg_pqueue_name = pqueue
pkg_pqueue_description = Erlang Priority Queues
pkg_pqueue_homepage = https://github.com/okeuday/pqueue
pkg_pqueue_fetch = git
pkg_pqueue_repo = https://github.com/okeuday/pqueue
pkg_pqueue_commit = master
PACKAGES += procket
pkg_procket_name = procket
pkg_procket_description = Erlang interface to low level socket operations
pkg_procket_homepage = http://blog.listincomprehension.com/search/label/procket
pkg_procket_fetch = git
pkg_procket_repo = https://github.com/msantos/procket
pkg_procket_commit = master
PACKAGES += proper
pkg_proper_name = proper
pkg_proper_description = PropEr: a QuickCheck-inspired property-based testing tool for Erlang.
pkg_proper_homepage = http://proper.softlab.ntua.gr
pkg_proper_fetch = git
pkg_proper_repo = https://github.com/manopapad/proper
pkg_proper_commit = master
PACKAGES += prop
pkg_prop_name = prop
pkg_prop_description = An Erlang code scaffolding and generator system.
pkg_prop_homepage = https://github.com/nuex/prop
pkg_prop_fetch = git
pkg_prop_repo = https://github.com/nuex/prop
pkg_prop_commit = master
PACKAGES += props
pkg_props_name = props
pkg_props_description = Property structure library
pkg_props_homepage = https://github.com/greyarea/props
pkg_props_fetch = git
pkg_props_repo = https://github.com/greyarea/props
pkg_props_commit = master
PACKAGES += protobuffs
pkg_protobuffs_name = protobuffs
pkg_protobuffs_description = An implementation of Google's Protocol Buffers for Erlang, based on ngerakines/erlang_protobuffs.
pkg_protobuffs_homepage = https://github.com/basho/erlang_protobuffs
pkg_protobuffs_fetch = git
pkg_protobuffs_repo = https://github.com/basho/erlang_protobuffs
pkg_protobuffs_commit = master
PACKAGES += psycho
pkg_psycho_name = psycho
pkg_psycho_description = HTTP server that provides a WSGI-like interface for applications and middleware.
pkg_psycho_homepage = https://github.com/gar1t/psycho
pkg_psycho_fetch = git
pkg_psycho_repo = https://github.com/gar1t/psycho
pkg_psycho_commit = master
PACKAGES += ptrackerl
pkg_ptrackerl_name = ptrackerl
pkg_ptrackerl_description = Pivotal Tracker API Client written in Erlang
pkg_ptrackerl_homepage = https://github.com/inaka/ptrackerl
pkg_ptrackerl_fetch = git
pkg_ptrackerl_repo = https://github.com/inaka/ptrackerl
pkg_ptrackerl_commit = master
PACKAGES += purity
pkg_purity_name = purity
pkg_purity_description = A side-effect analyzer for Erlang
pkg_purity_homepage = https://github.com/mpitid/purity
pkg_purity_fetch = git
pkg_purity_repo = https://github.com/mpitid/purity
pkg_purity_commit = master
PACKAGES += push_service
pkg_push_service_name = push_service
pkg_push_service_description = Push service
pkg_push_service_homepage = https://github.com/hairyhum/push_service
pkg_push_service_fetch = git
pkg_push_service_repo = https://github.com/hairyhum/push_service
pkg_push_service_commit = master
PACKAGES += qdate
pkg_qdate_name = qdate
pkg_qdate_description = Date, time, and timezone parsing, formatting, and conversion for Erlang.
pkg_qdate_homepage = https://github.com/choptastic/qdate
pkg_qdate_fetch = git
pkg_qdate_repo = https://github.com/choptastic/qdate
pkg_qdate_commit = 0.4.0
PACKAGES += qrcode
pkg_qrcode_name = qrcode
pkg_qrcode_description = QR Code encoder in Erlang
pkg_qrcode_homepage = https://github.com/komone/qrcode
pkg_qrcode_fetch = git
pkg_qrcode_repo = https://github.com/komone/qrcode
pkg_qrcode_commit = master
PACKAGES += quest
pkg_quest_name = quest
pkg_quest_description = Learn Erlang through this set of challenges. An interactive system for getting to know Erlang.
pkg_quest_homepage = https://github.com/eriksoe/ErlangQuest
pkg_quest_fetch = git
pkg_quest_repo = https://github.com/eriksoe/ErlangQuest
pkg_quest_commit = master
PACKAGES += quickrand
pkg_quickrand_name = quickrand
pkg_quickrand_description = Quick Erlang Random Number Generation
pkg_quickrand_homepage = https://github.com/okeuday/quickrand
pkg_quickrand_fetch = git
pkg_quickrand_repo = https://github.com/okeuday/quickrand
pkg_quickrand_commit = master
PACKAGES += rabbit_exchange_type_riak
pkg_rabbit_exchange_type_riak_name = rabbit_exchange_type_riak
pkg_rabbit_exchange_type_riak_description = Custom RabbitMQ exchange type for sticking messages in Riak
pkg_rabbit_exchange_type_riak_homepage = https://github.com/jbrisbin/riak-exchange
pkg_rabbit_exchange_type_riak_fetch = git
pkg_rabbit_exchange_type_riak_repo = https://github.com/jbrisbin/riak-exchange
pkg_rabbit_exchange_type_riak_commit = master
PACKAGES += rabbit
pkg_rabbit_name = rabbit
pkg_rabbit_description = RabbitMQ Server
pkg_rabbit_homepage = https://www.rabbitmq.com/
pkg_rabbit_fetch = git
pkg_rabbit_repo = https://github.com/rabbitmq/rabbitmq-server.git
pkg_rabbit_commit = master
PACKAGES += rack
pkg_rack_name = rack
pkg_rack_description = Rack handler for erlang
pkg_rack_homepage = https://github.com/erlyvideo/rack
pkg_rack_fetch = git
pkg_rack_repo = https://github.com/erlyvideo/rack
pkg_rack_commit = master
PACKAGES += radierl
pkg_radierl_name = radierl
pkg_radierl_description = RADIUS protocol stack implemented in Erlang.
pkg_radierl_homepage = https://github.com/vances/radierl
pkg_radierl_fetch = git
pkg_radierl_repo = https://github.com/vances/radierl
pkg_radierl_commit = master
PACKAGES += rafter
pkg_rafter_name = rafter
pkg_rafter_description = An Erlang library application which implements the Raft consensus protocol
pkg_rafter_homepage = https://github.com/andrewjstone/rafter
pkg_rafter_fetch = git
pkg_rafter_repo = https://github.com/andrewjstone/rafter
pkg_rafter_commit = master
PACKAGES += ranch
pkg_ranch_name = ranch
pkg_ranch_description = Socket acceptor pool for TCP protocols.
pkg_ranch_homepage = http://ninenines.eu
pkg_ranch_fetch = git
pkg_ranch_repo = https://github.com/ninenines/ranch
pkg_ranch_commit = 1.1.0
PACKAGES += rbeacon
pkg_rbeacon_name = rbeacon
pkg_rbeacon_description = LAN discovery and presence in Erlang.
pkg_rbeacon_homepage = https://github.com/refuge/rbeacon
pkg_rbeacon_fetch = git
pkg_rbeacon_repo = https://github.com/refuge/rbeacon
pkg_rbeacon_commit = master
PACKAGES += rebar
pkg_rebar_name = rebar
pkg_rebar_description = Erlang build tool that makes it easy to compile and test Erlang applications, port drivers and releases.
pkg_rebar_homepage = http://www.rebar3.org
pkg_rebar_fetch = git
pkg_rebar_repo = https://github.com/rebar/rebar3
pkg_rebar_commit = master
PACKAGES += rebus
pkg_rebus_name = rebus
pkg_rebus_description = A stupid simple, internal, pub/sub event bus written in- and for Erlang.
pkg_rebus_homepage = https://github.com/olle/rebus
pkg_rebus_fetch = git
pkg_rebus_repo = https://github.com/olle/rebus
pkg_rebus_commit = master
PACKAGES += rec2json
pkg_rec2json_name = rec2json
pkg_rec2json_description = Compile erlang record definitions into modules to convert them to/from json easily.
pkg_rec2json_homepage = https://github.com/lordnull/rec2json
pkg_rec2json_fetch = git
pkg_rec2json_repo = https://github.com/lordnull/rec2json
pkg_rec2json_commit = master
PACKAGES += recon
pkg_recon_name = recon
pkg_recon_description = Collection of functions and scripts to debug Erlang in production.
pkg_recon_homepage = https://github.com/ferd/recon
pkg_recon_fetch = git
pkg_recon_repo = https://github.com/ferd/recon
pkg_recon_commit = 2.2.1
PACKAGES += record_info
pkg_record_info_name = record_info
pkg_record_info_description = Convert between record and proplist
pkg_record_info_homepage = https://github.com/bipthelin/erlang-record_info
pkg_record_info_fetch = git
pkg_record_info_repo = https://github.com/bipthelin/erlang-record_info
pkg_record_info_commit = master
PACKAGES += redgrid
pkg_redgrid_name = redgrid
pkg_redgrid_description = automatic Erlang node discovery via redis
pkg_redgrid_homepage = https://github.com/jkvor/redgrid
pkg_redgrid_fetch = git
pkg_redgrid_repo = https://github.com/jkvor/redgrid
pkg_redgrid_commit = master
PACKAGES += redo
pkg_redo_name = redo
pkg_redo_description = pipelined erlang redis client
pkg_redo_homepage = https://github.com/jkvor/redo
pkg_redo_fetch = git
pkg_redo_repo = https://github.com/jkvor/redo
pkg_redo_commit = master
PACKAGES += reltool_util
pkg_reltool_util_name = reltool_util
pkg_reltool_util_description = Erlang reltool utility functionality application
pkg_reltool_util_homepage = https://github.com/okeuday/reltool_util
pkg_reltool_util_fetch = git
pkg_reltool_util_repo = https://github.com/okeuday/reltool_util
pkg_reltool_util_commit = master
PACKAGES += relx
pkg_relx_name = relx
pkg_relx_description = Sane, simple release creation for Erlang
pkg_relx_homepage = https://github.com/erlware/relx
pkg_relx_fetch = git
pkg_relx_repo = https://github.com/erlware/relx
pkg_relx_commit = master
PACKAGES += resource_discovery
pkg_resource_discovery_name = resource_discovery
pkg_resource_discovery_description = An application used to dynamically discover resources present in an Erlang node cluster.
pkg_resource_discovery_homepage = http://erlware.org/
pkg_resource_discovery_fetch = git
pkg_resource_discovery_repo = https://github.com/erlware/resource_discovery
pkg_resource_discovery_commit = master
PACKAGES += restc
pkg_restc_name = restc
pkg_restc_description = Erlang Rest Client
pkg_restc_homepage = https://github.com/kivra/restclient
pkg_restc_fetch = git
pkg_restc_repo = https://github.com/kivra/restclient
pkg_restc_commit = master
PACKAGES += rfc4627_jsonrpc
pkg_rfc4627_jsonrpc_name = rfc4627_jsonrpc
pkg_rfc4627_jsonrpc_description = Erlang RFC4627 (JSON) codec and JSON-RPC server implementation.
pkg_rfc4627_jsonrpc_homepage = https://github.com/tonyg/erlang-rfc4627
pkg_rfc4627_jsonrpc_fetch = git
pkg_rfc4627_jsonrpc_repo = https://github.com/tonyg/erlang-rfc4627
pkg_rfc4627_jsonrpc_commit = master
PACKAGES += riakc
pkg_riakc_name = riakc
pkg_riakc_description = Erlang clients for Riak.
pkg_riakc_homepage = https://github.com/basho/riak-erlang-client
pkg_riakc_fetch = git
pkg_riakc_repo = https://github.com/basho/riak-erlang-client
pkg_riakc_commit = master
PACKAGES += riak_control
pkg_riak_control_name = riak_control
pkg_riak_control_description = Webmachine-based administration interface for Riak.
pkg_riak_control_homepage = https://github.com/basho/riak_control
pkg_riak_control_fetch = git
pkg_riak_control_repo = https://github.com/basho/riak_control
pkg_riak_control_commit = master
PACKAGES += riak_core
pkg_riak_core_name = riak_core
pkg_riak_core_description = Distributed systems infrastructure used by Riak.
pkg_riak_core_homepage = https://github.com/basho/riak_core
pkg_riak_core_fetch = git
pkg_riak_core_repo = https://github.com/basho/riak_core
pkg_riak_core_commit = master
PACKAGES += riak_dt
pkg_riak_dt_name = riak_dt
pkg_riak_dt_description = Convergent replicated datatypes in Erlang
pkg_riak_dt_homepage = https://github.com/basho/riak_dt
pkg_riak_dt_fetch = git
pkg_riak_dt_repo = https://github.com/basho/riak_dt
pkg_riak_dt_commit = master
PACKAGES += riak_ensemble
pkg_riak_ensemble_name = riak_ensemble
pkg_riak_ensemble_description = Multi-Paxos framework in Erlang
pkg_riak_ensemble_homepage = https://github.com/basho/riak_ensemble
pkg_riak_ensemble_fetch = git
pkg_riak_ensemble_repo = https://github.com/basho/riak_ensemble
pkg_riak_ensemble_commit = master
PACKAGES += riakhttpc
pkg_riakhttpc_name = riakhttpc
pkg_riakhttpc_description = Riak Erlang client using the HTTP interface
pkg_riakhttpc_homepage = https://github.com/basho/riak-erlang-http-client
pkg_riakhttpc_fetch = git
pkg_riakhttpc_repo = https://github.com/basho/riak-erlang-http-client
pkg_riakhttpc_commit = master
PACKAGES += riak_kv
pkg_riak_kv_name = riak_kv
pkg_riak_kv_description = Riak Key/Value Store
pkg_riak_kv_homepage = https://github.com/basho/riak_kv
pkg_riak_kv_fetch = git
pkg_riak_kv_repo = https://github.com/basho/riak_kv
pkg_riak_kv_commit = master
PACKAGES += riaknostic
pkg_riaknostic_name = riaknostic
pkg_riaknostic_description = A diagnostic tool for Riak installations, to find common errors asap
pkg_riaknostic_homepage = https://github.com/basho/riaknostic
pkg_riaknostic_fetch = git
pkg_riaknostic_repo = https://github.com/basho/riaknostic
pkg_riaknostic_commit = master
PACKAGES += riak_pg
pkg_riak_pg_name = riak_pg
pkg_riak_pg_description = Distributed process groups with riak_core.
pkg_riak_pg_homepage = https://github.com/cmeiklejohn/riak_pg
pkg_riak_pg_fetch = git
pkg_riak_pg_repo = https://github.com/cmeiklejohn/riak_pg
pkg_riak_pg_commit = master
PACKAGES += riak_pipe
pkg_riak_pipe_name = riak_pipe
pkg_riak_pipe_description = Riak Pipelines
pkg_riak_pipe_homepage = https://github.com/basho/riak_pipe
pkg_riak_pipe_fetch = git
pkg_riak_pipe_repo = https://github.com/basho/riak_pipe
pkg_riak_pipe_commit = master
PACKAGES += riakpool
pkg_riakpool_name = riakpool
pkg_riakpool_description = erlang riak client pool
pkg_riakpool_homepage = https://github.com/dweldon/riakpool
pkg_riakpool_fetch = git
pkg_riakpool_repo = https://github.com/dweldon/riakpool
pkg_riakpool_commit = master
PACKAGES += riak_sysmon
pkg_riak_sysmon_name = riak_sysmon
pkg_riak_sysmon_description = Simple OTP app for managing Erlang VM system_monitor event messages
pkg_riak_sysmon_homepage = https://github.com/basho/riak_sysmon
pkg_riak_sysmon_fetch = git
pkg_riak_sysmon_repo = https://github.com/basho/riak_sysmon
pkg_riak_sysmon_commit = master
PACKAGES += riak_test
pkg_riak_test_name = riak_test
pkg_riak_test_description = I'm in your cluster, testing your riaks
pkg_riak_test_homepage = https://github.com/basho/riak_test
pkg_riak_test_fetch = git
pkg_riak_test_repo = https://github.com/basho/riak_test
pkg_riak_test_commit = master
PACKAGES += rivus_cep
pkg_rivus_cep_name = rivus_cep
pkg_rivus_cep_description = Complex event processing in Erlang
pkg_rivus_cep_homepage = https://github.com/vascokk/rivus_cep
pkg_rivus_cep_fetch = git
pkg_rivus_cep_repo = https://github.com/vascokk/rivus_cep
pkg_rivus_cep_commit = master
PACKAGES += rlimit
pkg_rlimit_name = rlimit
pkg_rlimit_description = Magnus Klaar's rate limiter code from etorrent
pkg_rlimit_homepage = https://github.com/jlouis/rlimit
pkg_rlimit_fetch = git
pkg_rlimit_repo = https://github.com/jlouis/rlimit
pkg_rlimit_commit = master
PACKAGES += safetyvalve
pkg_safetyvalve_name = safetyvalve
pkg_safetyvalve_description = A safety valve for your erlang node
pkg_safetyvalve_homepage = https://github.com/jlouis/safetyvalve
pkg_safetyvalve_fetch = git
pkg_safetyvalve_repo = https://github.com/jlouis/safetyvalve
pkg_safetyvalve_commit = master
PACKAGES += seestar
pkg_seestar_name = seestar
pkg_seestar_description = The Erlang client for Cassandra 1.2+ binary protocol
pkg_seestar_homepage = https://github.com/iamaleksey/seestar
pkg_seestar_fetch = git
pkg_seestar_repo = https://github.com/iamaleksey/seestar
pkg_seestar_commit = master
PACKAGES += service
pkg_service_name = service
pkg_service_description = A minimal Erlang behavior for creating CloudI internal services
pkg_service_homepage = http://cloudi.org/
pkg_service_fetch = git
pkg_service_repo = https://github.com/CloudI/service
pkg_service_commit = master
PACKAGES += setup
pkg_setup_name = setup
pkg_setup_description = Generic setup utility for Erlang-based systems
pkg_setup_homepage = https://github.com/uwiger/setup
pkg_setup_fetch = git
pkg_setup_repo = https://github.com/uwiger/setup
pkg_setup_commit = master
PACKAGES += sext
pkg_sext_name = sext
pkg_sext_description = Sortable Erlang Term Serialization
pkg_sext_homepage = https://github.com/uwiger/sext
pkg_sext_fetch = git
pkg_sext_repo = https://github.com/uwiger/sext
pkg_sext_commit = master
PACKAGES += sfmt
pkg_sfmt_name = sfmt
pkg_sfmt_description = SFMT pseudo random number generator for Erlang.
pkg_sfmt_homepage = https://github.com/jj1bdx/sfmt-erlang
pkg_sfmt_fetch = git
pkg_sfmt_repo = https://github.com/jj1bdx/sfmt-erlang
pkg_sfmt_commit = master
PACKAGES += sgte
pkg_sgte_name = sgte
pkg_sgte_description = A simple Erlang Template Engine
pkg_sgte_homepage = https://github.com/filippo/sgte
pkg_sgte_fetch = git
pkg_sgte_repo = https://github.com/filippo/sgte
pkg_sgte_commit = master
PACKAGES += sheriff
pkg_sheriff_name = sheriff
pkg_sheriff_description = Parse transform for type based validation.
pkg_sheriff_homepage = http://ninenines.eu
pkg_sheriff_fetch = git
pkg_sheriff_repo = https://github.com/extend/sheriff
pkg_sheriff_commit = master
PACKAGES += shotgun
pkg_shotgun_name = shotgun
pkg_shotgun_description = better than just a gun
pkg_shotgun_homepage = https://github.com/inaka/shotgun
pkg_shotgun_fetch = git
pkg_shotgun_repo = https://github.com/inaka/shotgun
pkg_shotgun_commit = 0.1.0
PACKAGES += sidejob
pkg_sidejob_name = sidejob
pkg_sidejob_description = Parallel worker and capacity limiting library for Erlang
pkg_sidejob_homepage = https://github.com/basho/sidejob
pkg_sidejob_fetch = git
pkg_sidejob_repo = https://github.com/basho/sidejob
pkg_sidejob_commit = master
PACKAGES += sieve
pkg_sieve_name = sieve
pkg_sieve_description = sieve is a simple TCP routing proxy (layer 7) in erlang
pkg_sieve_homepage = https://github.com/benoitc/sieve
pkg_sieve_fetch = git
pkg_sieve_repo = https://github.com/benoitc/sieve
pkg_sieve_commit = master
PACKAGES += sighandler
pkg_sighandler_name = sighandler
pkg_sighandler_description = Handle UNIX signals in Er lang
pkg_sighandler_homepage = https://github.com/jkingsbery/sighandler
pkg_sighandler_fetch = git
pkg_sighandler_repo = https://github.com/jkingsbery/sighandler
pkg_sighandler_commit = master
PACKAGES += simhash
pkg_simhash_name = simhash
pkg_simhash_description = Simhashing for Erlang -- hashing algorithm to find near-duplicates in binary data.
pkg_simhash_homepage = https://github.com/ferd/simhash
pkg_simhash_fetch = git
pkg_simhash_repo = https://github.com/ferd/simhash
pkg_simhash_commit = master
PACKAGES += simple_bridge
pkg_simple_bridge_name = simple_bridge
pkg_simple_bridge_description = A simple, standardized interface library to Erlang HTTP Servers.
pkg_simple_bridge_homepage = https://github.com/nitrogen/simple_bridge
pkg_simple_bridge_fetch = git
pkg_simple_bridge_repo = https://github.com/nitrogen/simple_bridge
pkg_simple_bridge_commit = master
PACKAGES += simple_oauth2
pkg_simple_oauth2_name = simple_oauth2
pkg_simple_oauth2_description = Simple erlang OAuth2 client module for any http server framework (Google, Facebook, Yandex, Vkontakte are preconfigured)
pkg_simple_oauth2_homepage = https://github.com/virtan/simple_oauth2
pkg_simple_oauth2_fetch = git
pkg_simple_oauth2_repo = https://github.com/virtan/simple_oauth2
pkg_simple_oauth2_commit = master
PACKAGES += skel
pkg_skel_name = skel
pkg_skel_description = A Streaming Process-based Skeleton Library for Erlang
pkg_skel_homepage = https://github.com/ParaPhrase/skel
pkg_skel_fetch = git
pkg_skel_repo = https://github.com/ParaPhrase/skel
pkg_skel_commit = master
PACKAGES += smother
pkg_smother_name = smother
pkg_smother_description = Extended code coverage metrics for Erlang.
pkg_smother_homepage = https://ramsay-t.github.io/Smother/
pkg_smother_fetch = git
pkg_smother_repo = https://github.com/ramsay-t/Smother
pkg_smother_commit = master
PACKAGES += social
pkg_social_name = social
pkg_social_description = Cowboy handler for social login via OAuth2 providers
pkg_social_homepage = https://github.com/dvv/social
pkg_social_fetch = git
pkg_social_repo = https://github.com/dvv/social
pkg_social_commit = master
PACKAGES += spapi_router
pkg_spapi_router_name = spapi_router
pkg_spapi_router_description = Partially-connected Erlang clustering
pkg_spapi_router_homepage = https://github.com/spilgames/spapi-router
pkg_spapi_router_fetch = git
pkg_spapi_router_repo = https://github.com/spilgames/spapi-router
pkg_spapi_router_commit = master
PACKAGES += sqerl
pkg_sqerl_name = sqerl
pkg_sqerl_description = An Erlang-flavoured SQL DSL
pkg_sqerl_homepage = https://github.com/hairyhum/sqerl
pkg_sqerl_fetch = git
pkg_sqerl_repo = https://github.com/hairyhum/sqerl
pkg_sqerl_commit = master
PACKAGES += srly
pkg_srly_name = srly
pkg_srly_description = Native Erlang Unix serial interface
pkg_srly_homepage = https://github.com/msantos/srly
pkg_srly_fetch = git
pkg_srly_repo = https://github.com/msantos/srly
pkg_srly_commit = master
PACKAGES += sshrpc
pkg_sshrpc_name = sshrpc
pkg_sshrpc_description = Erlang SSH RPC module (experimental)
pkg_sshrpc_homepage = https://github.com/jj1bdx/sshrpc
pkg_sshrpc_fetch = git
pkg_sshrpc_repo = https://github.com/jj1bdx/sshrpc
pkg_sshrpc_commit = master
PACKAGES += stable
pkg_stable_name = stable
pkg_stable_description = Library of assorted helpers for Cowboy web server.
pkg_stable_homepage = https://github.com/dvv/stable
pkg_stable_fetch = git
pkg_stable_repo = https://github.com/dvv/stable
pkg_stable_commit = master
PACKAGES += statebox
pkg_statebox_name = statebox
pkg_statebox_description = Erlang state monad with merge/conflict-resolution capabilities. Useful for Riak.
pkg_statebox_homepage = https://github.com/mochi/statebox
pkg_statebox_fetch = git
pkg_statebox_repo = https://github.com/mochi/statebox
pkg_statebox_commit = master
PACKAGES += statebox_riak
pkg_statebox_riak_name = statebox_riak
pkg_statebox_riak_description = Convenience library that makes it easier to use statebox with riak, extracted from best practices in our production code at Mochi Media.
pkg_statebox_riak_homepage = https://github.com/mochi/statebox_riak
pkg_statebox_riak_fetch = git
pkg_statebox_riak_repo = https://github.com/mochi/statebox_riak
pkg_statebox_riak_commit = master
PACKAGES += statman
pkg_statman_name = statman
pkg_statman_description = Efficiently collect massive volumes of metrics inside the Erlang VM
pkg_statman_homepage = https://github.com/knutin/statman
pkg_statman_fetch = git
pkg_statman_repo = https://github.com/knutin/statman
pkg_statman_commit = master
PACKAGES += statsderl
pkg_statsderl_name = statsderl
pkg_statsderl_description = StatsD client (erlang)
pkg_statsderl_homepage = https://github.com/lpgauth/statsderl
pkg_statsderl_fetch = git
pkg_statsderl_repo = https://github.com/lpgauth/statsderl
pkg_statsderl_commit = master
PACKAGES += stdinout_pool
pkg_stdinout_pool_name = stdinout_pool
pkg_stdinout_pool_description = stdinout_pool : stuff goes in, stuff goes out. there's never any miscommunication.
pkg_stdinout_pool_homepage = https://github.com/mattsta/erlang-stdinout-pool
pkg_stdinout_pool_fetch = git
pkg_stdinout_pool_repo = https://github.com/mattsta/erlang-stdinout-pool
pkg_stdinout_pool_commit = master
PACKAGES += stockdb
pkg_stockdb_name = stockdb
pkg_stockdb_description = Database for storing Stock Exchange quotes in erlang
pkg_stockdb_homepage = https://github.com/maxlapshin/stockdb
pkg_stockdb_fetch = git
pkg_stockdb_repo = https://github.com/maxlapshin/stockdb
pkg_stockdb_commit = master
PACKAGES += stripe
pkg_stripe_name = stripe
pkg_stripe_description = Erlang interface to the stripe.com API
pkg_stripe_homepage = https://github.com/mattsta/stripe-erlang
pkg_stripe_fetch = git
pkg_stripe_repo = https://github.com/mattsta/stripe-erlang
pkg_stripe_commit = v1
PACKAGES += surrogate
pkg_surrogate_name = surrogate
pkg_surrogate_description = Proxy server written in erlang. Supports reverse proxy load balancing and forward proxy with http (including CONNECT), socks4, socks5, and transparent proxy modes.
pkg_surrogate_homepage = https://github.com/skruger/Surrogate
pkg_surrogate_fetch = git
pkg_surrogate_repo = https://github.com/skruger/Surrogate
pkg_surrogate_commit = master
PACKAGES += swab
pkg_swab_name = swab
pkg_swab_description = General purpose buffer handling module
pkg_swab_homepage = https://github.com/crownedgrouse/swab
pkg_swab_fetch = git
pkg_swab_repo = https://github.com/crownedgrouse/swab
pkg_swab_commit = master
PACKAGES += swarm
pkg_swarm_name = swarm
pkg_swarm_description = Fast and simple acceptor pool for Erlang
pkg_swarm_homepage = https://github.com/jeremey/swarm
pkg_swarm_fetch = git
pkg_swarm_repo = https://github.com/jeremey/swarm
pkg_swarm_commit = master
PACKAGES += switchboard
pkg_switchboard_name = switchboard
pkg_switchboard_description = A framework for processing email using worker plugins.
pkg_switchboard_homepage = https://github.com/thusfresh/switchboard
pkg_switchboard_fetch = git
pkg_switchboard_repo = https://github.com/thusfresh/switchboard
pkg_switchboard_commit = master
PACKAGES += sync
pkg_sync_name = sync
pkg_sync_description = On-the-fly recompiling and reloading in Erlang.
pkg_sync_homepage = https://github.com/rustyio/sync
pkg_sync_fetch = git
pkg_sync_repo = https://github.com/rustyio/sync
pkg_sync_commit = master
PACKAGES += syn
pkg_syn_name = syn
pkg_syn_description = A global process registry for Erlang.
pkg_syn_homepage = https://github.com/ostinelli/syn
pkg_syn_fetch = git
pkg_syn_repo = https://github.com/ostinelli/syn
pkg_syn_commit = master
PACKAGES += syntaxerl
pkg_syntaxerl_name = syntaxerl
pkg_syntaxerl_description = Syntax checker for Erlang
pkg_syntaxerl_homepage = https://github.com/ten0s/syntaxerl
pkg_syntaxerl_fetch = git
pkg_syntaxerl_repo = https://github.com/ten0s/syntaxerl
pkg_syntaxerl_commit = master
PACKAGES += syslog
pkg_syslog_name = syslog
pkg_syslog_description = Erlang port driver for interacting with syslog via syslog(3)
pkg_syslog_homepage = https://github.com/Vagabond/erlang-syslog
pkg_syslog_fetch = git
pkg_syslog_repo = https://github.com/Vagabond/erlang-syslog
pkg_syslog_commit = master
PACKAGES += taskforce
pkg_taskforce_name = taskforce
pkg_taskforce_description = Erlang worker pools for controlled parallelisation of arbitrary tasks.
pkg_taskforce_homepage = https://github.com/g-andrade/taskforce
pkg_taskforce_fetch = git
pkg_taskforce_repo = https://github.com/g-andrade/taskforce
pkg_taskforce_commit = master
PACKAGES += tddreloader
pkg_tddreloader_name = tddreloader
pkg_tddreloader_description = Shell utility for recompiling, reloading, and testing code as it changes
pkg_tddreloader_homepage = https://github.com/version2beta/tddreloader
pkg_tddreloader_fetch = git
pkg_tddreloader_repo = https://github.com/version2beta/tddreloader
pkg_tddreloader_commit = master
PACKAGES += tempo
pkg_tempo_name = tempo
pkg_tempo_description = NIF-based date and time parsing and formatting for Erlang.
pkg_tempo_homepage = https://github.com/selectel/tempo
pkg_tempo_fetch = git
pkg_tempo_repo = https://github.com/selectel/tempo
pkg_tempo_commit = master
PACKAGES += ticktick
pkg_ticktick_name = ticktick
pkg_ticktick_description = Ticktick is an id generator for message service.
pkg_ticktick_homepage = https://github.com/ericliang/ticktick
pkg_ticktick_fetch = git
pkg_ticktick_repo = https://github.com/ericliang/ticktick
pkg_ticktick_commit = master
PACKAGES += tinymq
pkg_tinymq_name = tinymq
pkg_tinymq_description = TinyMQ - a diminutive, in-memory message queue
pkg_tinymq_homepage = https://github.com/ChicagoBoss/tinymq
pkg_tinymq_fetch = git
pkg_tinymq_repo = https://github.com/ChicagoBoss/tinymq
pkg_tinymq_commit = master
PACKAGES += tinymt
pkg_tinymt_name = tinymt
pkg_tinymt_description = TinyMT pseudo random number generator for Erlang.
pkg_tinymt_homepage = https://github.com/jj1bdx/tinymt-erlang
pkg_tinymt_fetch = git
pkg_tinymt_repo = https://github.com/jj1bdx/tinymt-erlang
pkg_tinymt_commit = master
PACKAGES += tirerl
pkg_tirerl_name = tirerl
pkg_tirerl_description = Erlang interface to Elastic Search
pkg_tirerl_homepage = https://github.com/inaka/tirerl
pkg_tirerl_fetch = git
pkg_tirerl_repo = https://github.com/inaka/tirerl
pkg_tirerl_commit = master
PACKAGES += traffic_tools
pkg_traffic_tools_name = traffic_tools
pkg_traffic_tools_description = Simple traffic limiting library
pkg_traffic_tools_homepage = https://github.com/systra/traffic_tools
pkg_traffic_tools_fetch = git
pkg_traffic_tools_repo = https://github.com/systra/traffic_tools
pkg_traffic_tools_commit = master
PACKAGES += trails
pkg_trails_name = trails
pkg_trails_description = A couple of improvements over Cowboy Routes
pkg_trails_homepage = http://inaka.github.io/cowboy-trails/
pkg_trails_fetch = git
pkg_trails_repo = https://github.com/inaka/cowboy-trails
pkg_trails_commit = master
PACKAGES += trane
pkg_trane_name = trane
pkg_trane_description = SAX style broken HTML parser in Erlang
pkg_trane_homepage = https://github.com/massemanet/trane
pkg_trane_fetch = git
pkg_trane_repo = https://github.com/massemanet/trane
pkg_trane_commit = master
PACKAGES += transit
pkg_transit_name = transit
pkg_transit_description = transit format for erlang
pkg_transit_homepage = https://github.com/isaiah/transit-erlang
pkg_transit_fetch = git
pkg_transit_repo = https://github.com/isaiah/transit-erlang
pkg_transit_commit = master
PACKAGES += trie
pkg_trie_name = trie
pkg_trie_description = Erlang Trie Implementation
pkg_trie_homepage = https://github.com/okeuday/trie
pkg_trie_fetch = git
pkg_trie_repo = https://github.com/okeuday/trie
pkg_trie_commit = master
PACKAGES += triq
pkg_triq_name = triq
pkg_triq_description = Trifork QuickCheck
pkg_triq_homepage = https://github.com/krestenkrab/triq
pkg_triq_fetch = git
pkg_triq_repo = https://github.com/krestenkrab/triq
pkg_triq_commit = master
PACKAGES += tunctl
pkg_tunctl_name = tunctl
pkg_tunctl_description = Erlang TUN/TAP interface
pkg_tunctl_homepage = https://github.com/msantos/tunctl
pkg_tunctl_fetch = git
pkg_tunctl_repo = https://github.com/msantos/tunctl
pkg_tunctl_commit = master
PACKAGES += twerl
pkg_twerl_name = twerl
pkg_twerl_description = Erlang client for the Twitter Streaming API
pkg_twerl_homepage = https://github.com/lucaspiller/twerl
pkg_twerl_fetch = git
pkg_twerl_repo = https://github.com/lucaspiller/twerl
pkg_twerl_commit = oauth
PACKAGES += twitter_erlang
pkg_twitter_erlang_name = twitter_erlang
pkg_twitter_erlang_description = An Erlang twitter client
pkg_twitter_erlang_homepage = https://github.com/ngerakines/erlang_twitter
pkg_twitter_erlang_fetch = git
pkg_twitter_erlang_repo = https://github.com/ngerakines/erlang_twitter
pkg_twitter_erlang_commit = master
PACKAGES += ucol_nif
pkg_ucol_nif_name = ucol_nif
pkg_ucol_nif_description = ICU based collation Erlang module
pkg_ucol_nif_homepage = https://github.com/refuge/ucol_nif
pkg_ucol_nif_fetch = git
pkg_ucol_nif_repo = https://github.com/refuge/ucol_nif
pkg_ucol_nif_commit = master
PACKAGES += unicorn
pkg_unicorn_name = unicorn
pkg_unicorn_description = Generic configuration server
pkg_unicorn_homepage = https://github.com/shizzard/unicorn
pkg_unicorn_fetch = git
pkg_unicorn_repo = https://github.com/shizzard/unicorn
pkg_unicorn_commit = 0.3.0
PACKAGES += unsplit
pkg_unsplit_name = unsplit
pkg_unsplit_description = Resolves conflicts in Mnesia after network splits
pkg_unsplit_homepage = https://github.com/uwiger/unsplit
pkg_unsplit_fetch = git
pkg_unsplit_repo = https://github.com/uwiger/unsplit
pkg_unsplit_commit = master
PACKAGES += uuid
pkg_uuid_name = uuid
pkg_uuid_description = Erlang UUID Implementation
pkg_uuid_homepage = https://github.com/okeuday/uuid
pkg_uuid_fetch = git
pkg_uuid_repo = https://github.com/okeuday/uuid
pkg_uuid_commit = v1.4.0
PACKAGES += ux
pkg_ux_name = ux
pkg_ux_description = Unicode eXtention for Erlang (Strings, Collation)
pkg_ux_homepage = https://github.com/erlang-unicode/ux
pkg_ux_fetch = git
pkg_ux_repo = https://github.com/erlang-unicode/ux
pkg_ux_commit = master
PACKAGES += vert
pkg_vert_name = vert
pkg_vert_description = Erlang binding to libvirt virtualization API
pkg_vert_homepage = https://github.com/msantos/erlang-libvirt
pkg_vert_fetch = git
pkg_vert_repo = https://github.com/msantos/erlang-libvirt
pkg_vert_commit = master
PACKAGES += verx
pkg_verx_name = verx
pkg_verx_description = Erlang implementation of the libvirtd remote protocol
pkg_verx_homepage = https://github.com/msantos/verx
pkg_verx_fetch = git
pkg_verx_repo = https://github.com/msantos/verx
pkg_verx_commit = master
PACKAGES += vmq_acl
pkg_vmq_acl_name = vmq_acl
pkg_vmq_acl_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_acl_homepage = https://verne.mq/
pkg_vmq_acl_fetch = git
pkg_vmq_acl_repo = https://github.com/erlio/vmq_acl
pkg_vmq_acl_commit = master
PACKAGES += vmq_bridge
pkg_vmq_bridge_name = vmq_bridge
pkg_vmq_bridge_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_bridge_homepage = https://verne.mq/
pkg_vmq_bridge_fetch = git
pkg_vmq_bridge_repo = https://github.com/erlio/vmq_bridge
pkg_vmq_bridge_commit = master
PACKAGES += vmq_graphite
pkg_vmq_graphite_name = vmq_graphite
pkg_vmq_graphite_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_graphite_homepage = https://verne.mq/
pkg_vmq_graphite_fetch = git
pkg_vmq_graphite_repo = https://github.com/erlio/vmq_graphite
pkg_vmq_graphite_commit = master
PACKAGES += vmq_passwd
pkg_vmq_passwd_name = vmq_passwd
pkg_vmq_passwd_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_passwd_homepage = https://verne.mq/
pkg_vmq_passwd_fetch = git
pkg_vmq_passwd_repo = https://github.com/erlio/vmq_passwd
pkg_vmq_passwd_commit = master
PACKAGES += vmq_server
pkg_vmq_server_name = vmq_server
pkg_vmq_server_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_server_homepage = https://verne.mq/
pkg_vmq_server_fetch = git
pkg_vmq_server_repo = https://github.com/erlio/vmq_server
pkg_vmq_server_commit = master
PACKAGES += vmq_snmp
pkg_vmq_snmp_name = vmq_snmp
pkg_vmq_snmp_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_snmp_homepage = https://verne.mq/
pkg_vmq_snmp_fetch = git
pkg_vmq_snmp_repo = https://github.com/erlio/vmq_snmp
pkg_vmq_snmp_commit = master
PACKAGES += vmq_systree
pkg_vmq_systree_name = vmq_systree
pkg_vmq_systree_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_systree_homepage = https://verne.mq/
pkg_vmq_systree_fetch = git
pkg_vmq_systree_repo = https://github.com/erlio/vmq_systree
pkg_vmq_systree_commit = master
PACKAGES += vmstats
pkg_vmstats_name = vmstats
pkg_vmstats_description = tiny Erlang app that works in conjunction with statsderl in order to generate information on the Erlang VM for graphite logs.
pkg_vmstats_homepage = https://github.com/ferd/vmstats
pkg_vmstats_fetch = git
pkg_vmstats_repo = https://github.com/ferd/vmstats
pkg_vmstats_commit = master
PACKAGES += walrus
pkg_walrus_name = walrus
pkg_walrus_description = Walrus - Mustache-like Templating
pkg_walrus_homepage = https://github.com/devinus/walrus
pkg_walrus_fetch = git
pkg_walrus_repo = https://github.com/devinus/walrus
pkg_walrus_commit = master
PACKAGES += webmachine
pkg_webmachine_name = webmachine
pkg_webmachine_description = A REST-based system for building web applications.
pkg_webmachine_homepage = https://github.com/basho/webmachine
pkg_webmachine_fetch = git
pkg_webmachine_repo = https://github.com/basho/webmachine
pkg_webmachine_commit = master
PACKAGES += websocket_client
pkg_websocket_client_name = websocket_client
pkg_websocket_client_description = Erlang websocket client (ws and wss supported)
pkg_websocket_client_homepage = https://github.com/jeremyong/websocket_client
pkg_websocket_client_fetch = git
pkg_websocket_client_repo = https://github.com/jeremyong/websocket_client
pkg_websocket_client_commit = master
PACKAGES += worker_pool
pkg_worker_pool_name = worker_pool
pkg_worker_pool_description = a simple erlang worker pool
pkg_worker_pool_homepage = https://github.com/inaka/worker_pool
pkg_worker_pool_fetch = git
pkg_worker_pool_repo = https://github.com/inaka/worker_pool
pkg_worker_pool_commit = 1.0.2
PACKAGES += wrangler
pkg_wrangler_name = wrangler
pkg_wrangler_description = Import of the Wrangler svn repository.
pkg_wrangler_homepage = http://www.cs.kent.ac.uk/projects/wrangler/Home.html
pkg_wrangler_fetch = git
pkg_wrangler_repo = https://github.com/RefactoringTools/wrangler
pkg_wrangler_commit = master
PACKAGES += wsock
pkg_wsock_name = wsock
pkg_wsock_description = Erlang library to build WebSocket clients and servers
pkg_wsock_homepage = https://github.com/madtrick/wsock
pkg_wsock_fetch = git
pkg_wsock_repo = https://github.com/madtrick/wsock
pkg_wsock_commit = master
PACKAGES += xhttpc
pkg_xhttpc_name = xhttpc
pkg_xhttpc_description = Extensible HTTP Client for Erlang
pkg_xhttpc_homepage = https://github.com/seriyps/xhttpc
pkg_xhttpc_fetch = git
pkg_xhttpc_repo = https://github.com/seriyps/xhttpc
pkg_xhttpc_commit = master
PACKAGES += xref_runner
pkg_xref_runner_name = xref_runner
pkg_xref_runner_description = Erlang Xref Runner (inspired in rebar xref)
pkg_xref_runner_homepage = https://github.com/inaka/xref_runner
pkg_xref_runner_fetch = git
pkg_xref_runner_repo = https://github.com/inaka/xref_runner
pkg_xref_runner_commit = 0.2.0
PACKAGES += yamerl
pkg_yamerl_name = yamerl
pkg_yamerl_description = YAML 1.2 parser in pure Erlang
pkg_yamerl_homepage = https://github.com/yakaz/yamerl
pkg_yamerl_fetch = git
pkg_yamerl_repo = https://github.com/yakaz/yamerl
pkg_yamerl_commit = master
PACKAGES += yamler
pkg_yamler_name = yamler
pkg_yamler_description = libyaml-based yaml loader for Erlang
pkg_yamler_homepage = https://github.com/goertzenator/yamler
pkg_yamler_fetch = git
pkg_yamler_repo = https://github.com/goertzenator/yamler
pkg_yamler_commit = master
PACKAGES += yaws
pkg_yaws_name = yaws
pkg_yaws_description = Yaws webserver
pkg_yaws_homepage = http://yaws.hyber.org
pkg_yaws_fetch = git
pkg_yaws_repo = https://github.com/klacke/yaws
pkg_yaws_commit = master
PACKAGES += zab_engine
pkg_zab_engine_name = zab_engine
pkg_zab_engine_description = zab propotocol implement by erlang
pkg_zab_engine_homepage = https://github.com/xinmingyao/zab_engine
pkg_zab_engine_fetch = git
pkg_zab_engine_repo = https://github.com/xinmingyao/zab_engine
pkg_zab_engine_commit = master
PACKAGES += zeta
pkg_zeta_name = zeta
pkg_zeta_description = HTTP access log parser in Erlang
pkg_zeta_homepage = https://github.com/s1n4/zeta
pkg_zeta_fetch = git
pkg_zeta_repo = https://github.com/s1n4/zeta
pkg_zeta_commit =
PACKAGES += zippers
pkg_zippers_name = zippers
pkg_zippers_description = A library for functional zipper data structures in Erlang. Read more on zippers
pkg_zippers_homepage = https://github.com/ferd/zippers
pkg_zippers_fetch = git
pkg_zippers_repo = https://github.com/ferd/zippers
pkg_zippers_commit = master
PACKAGES += zlists
pkg_zlists_name = zlists
pkg_zlists_description = Erlang lazy lists library.
pkg_zlists_homepage = https://github.com/vjache/erlang-zlists
pkg_zlists_fetch = git
pkg_zlists_repo = https://github.com/vjache/erlang-zlists
pkg_zlists_commit = master
PACKAGES += zraft_lib
pkg_zraft_lib_name = zraft_lib
pkg_zraft_lib_description = Erlang raft consensus protocol implementation
pkg_zraft_lib_homepage = https://github.com/dreyk/zraft_lib
pkg_zraft_lib_fetch = git
pkg_zraft_lib_repo = https://github.com/dreyk/zraft_lib
pkg_zraft_lib_commit = master
PACKAGES += zucchini
pkg_zucchini_name = zucchini
pkg_zucchini_description = An Erlang INI parser
pkg_zucchini_homepage = https://github.com/devinus/zucchini
pkg_zucchini_fetch = git
pkg_zucchini_repo = https://github.com/devinus/zucchini
pkg_zucchini_commit = master
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: search
define pkg_print
$(verbose) printf "%s\n" \
$(if $(call core_eq,$(1),$(pkg_$(1)_name)),,"Pkg name: $(1)") \
"App name: $(pkg_$(1)_name)" \
"Description: $(pkg_$(1)_description)" \
"Home page: $(pkg_$(1)_homepage)" \
"Fetch with: $(pkg_$(1)_fetch)" \
"Repository: $(pkg_$(1)_repo)" \
"Commit: $(pkg_$(1)_commit)" \
""
endef
search:
ifdef q
$(foreach p,$(PACKAGES), \
$(if $(findstring $(call core_lc,$(q)),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \
$(call pkg_print,$(p))))
else
$(foreach p,$(PACKAGES),$(call pkg_print,$(p)))
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-deps distclean-pkg
# Configuration.
IGNORE_DEPS ?=
DEPS_DIR ?= $(CURDIR)/deps
export DEPS_DIR
REBAR_DEPS_DIR = $(DEPS_DIR)
export REBAR_DEPS_DIR
ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(filter-out $(IGNORE_DEPS),$(DEPS)))
ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),)
ifeq ($(ERL_LIBS),)
ERL_LIBS = $(DEPS_DIR)
else
ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)
endif
endif
export ERL_LIBS
# Verbosity.
dep_verbose_0 = @echo " DEP " $(1);
dep_verbose = $(dep_verbose_$(V))
# Core targets.
ifneq ($(SKIP_DEPS),)
deps::
else
deps:: $(ALL_DEPS_DIRS)
ifneq ($(IS_DEP),1)
$(verbose) rm -f $(ERLANG_MK_TMP)/deps.log
endif
$(verbose) mkdir -p $(ERLANG_MK_TMP)
$(verbose) for dep in $(ALL_DEPS_DIRS) ; do \
if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \
echo -n; \
else \
echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \
if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \
$(MAKE) -C $$dep IS_DEP=1 || exit $$?; \
else \
echo "ERROR: No Makefile to build dependency $$dep."; \
exit 1; \
fi \
fi \
done
endif
distclean:: distclean-deps distclean-pkg
# Deps related targets.
# @todo rename GNUmakefile and makefile into Makefile first, if they exist
# While Makefile file could be GNUmakefile or makefile,
# in practice only Makefile is needed so far.
define dep_autopatch
if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
if [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \
$(call dep_autopatch2,$(1)); \
elif [ 0 != `grep -ci rebar $(DEPS_DIR)/$(1)/Makefile` ]; then \
$(call dep_autopatch2,$(1)); \
elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk -exec grep -i rebar '{}' \;`" ]; then \
$(call dep_autopatch2,$(1)); \
else \
if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \
$(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
$(call dep_autopatch_erlang_mk,$(1)); \
else \
$(call erlang,$(call dep_autopatch_app.erl,$(1))); \
fi \
fi \
else \
if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \
$(call dep_autopatch_noop,$(1)); \
else \
$(call dep_autopatch2,$(1)); \
fi \
fi
endef
define dep_autopatch2
$(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
if [ -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script ]; then \
$(call dep_autopatch_fetch_rebar); \
$(call dep_autopatch_rebar,$(1)); \
else \
$(call dep_autopatch_gen,$(1)); \
fi
endef
define dep_autopatch_noop
printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile
endef
# Overwrite erlang.mk with the current file by default.
ifeq ($(NO_AUTOPATCH_ERLANG_MK),)
define dep_autopatch_erlang_mk
echo "include $(ERLANG_MK_FILENAME)" > $(DEPS_DIR)/$(1)/erlang.mk
endef
else
define dep_autopatch_erlang_mk
echo -n
endef
endif
define dep_autopatch_gen
printf "%s\n" \
"ERLC_OPTS = +debug_info" \
"include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile
endef
define dep_autopatch_fetch_rebar
mkdir -p $(ERLANG_MK_TMP); \
if [ ! -d $(ERLANG_MK_TMP)/rebar ]; then \
git clone -q -n -- https://github.com/rebar/rebar $(ERLANG_MK_TMP)/rebar; \
cd $(ERLANG_MK_TMP)/rebar; \
git checkout -q 791db716b5a3a7671e0b351f95ddf24b848ee173; \
$(MAKE); \
cd -; \
fi
endef
define dep_autopatch_rebar
if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \
fi; \
$(call erlang,$(call dep_autopatch_rebar.erl,$(1))); \
rm -f $(DEPS_DIR)/$(1)/ebin/$(1).app
endef
define dep_autopatch_rebar.erl
application:set_env(rebar, log_level, debug),
Conf1 = case file:consult("$(DEPS_DIR)/$(1)/rebar.config") of
{ok, Conf0} -> Conf0;
_ -> []
end,
{Conf, OsEnv} = fun() ->
case filelib:is_file("$(DEPS_DIR)/$(1)/rebar.config.script") of
false -> {Conf1, []};
true ->
Bindings0 = erl_eval:new_bindings(),
Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0),
Bindings = erl_eval:add_binding('SCRIPT', "$(DEPS_DIR)/$(1)/rebar.config.script", Bindings1),
Before = os:getenv(),
{ok, Conf2} = file:script("$(DEPS_DIR)/$(1)/rebar.config.script", Bindings),
{Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)}
end
end(),
Write = fun (Text) ->
file:write_file("$(DEPS_DIR)/$(1)/Makefile", Text, [append])
end,
Escape = fun (Text) ->
re:replace(Text, "\\\\$$$$", "\$$$$$$$$", [global, {return, list}])
end,
Write("IGNORE_DEPS = edown eper eunit_formatters meck node_package "
"rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"),
Write("C_SRC_DIR = /path/do/not/exist\n"),
Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"),
Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]),
fun() ->
Write("ERLC_OPTS = +debug_info\nexport ERLC_OPTS\n"),
case lists:keyfind(erl_opts, 1, Conf) of
false -> ok;
{_, ErlOpts} ->
lists:foreach(fun
({d, D}) ->
Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
({i, I}) ->
Write(["ERLC_OPTS += -I ", I, "\n"]);
({platform_define, Regex, D}) ->
case rebar_utils:is_arch(Regex) of
true -> Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
false -> ok
end;
({parse_transform, PT}) ->
Write("ERLC_OPTS += +'{parse_transform, " ++ atom_to_list(PT) ++ "}'\n");
(_) -> ok
end, ErlOpts)
end,
Write("\n")
end(),
fun() ->
File = case lists:keyfind(deps, 1, Conf) of
false -> [];
{_, Deps} ->
[begin case case Dep of
{N, S} when is_atom(N), is_list(S) -> {N, {hex, S}};
{N, S} when is_tuple(S) -> {N, S};
{N, _, S} -> {N, S};
{N, _, S, _} -> {N, S};
_ -> false
end of
false -> ok;
{Name, Source} ->
{Method, Repo, Commit} = case Source of
{hex, V} -> {hex, V, undefined};
{git, R} -> {git, R, master};
{M, R, {branch, C}} -> {M, R, C};
{M, R, {ref, C}} -> {M, R, C};
{M, R, {tag, C}} -> {M, R, C};
{M, R, C} -> {M, R, C}
end,
Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit]))
end end || Dep <- Deps]
end
end(),
fun() ->
case lists:keyfind(erl_first_files, 1, Conf) of
false -> ok;
{_, Files} ->
Names = [[" ", case lists:reverse(F) of
"lre." ++ Elif -> lists:reverse(Elif);
Elif -> lists:reverse(Elif)
end] || "src/" ++ F <- Files],
Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names]))
end
end(),
FindFirst = fun(F, Fd) ->
case io:parse_erl_form(Fd, undefined) of
{ok, {attribute, _, compile, {parse_transform, PT}}, _} ->
[PT, F(F, Fd)];
{ok, {attribute, _, compile, CompileOpts}, _} when is_list(CompileOpts) ->
case proplists:get_value(parse_transform, CompileOpts) of
undefined -> [F(F, Fd)];
PT -> [PT, F(F, Fd)]
end;
{ok, {attribute, _, include, Hrl}, _} ->
case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ ->
case file:open("$(DEPS_DIR)/$(1)/src/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ -> [F(F, Fd)]
end
end;
{ok, {attribute, _, include_lib, "$(1)/include/" ++ Hrl}, _} ->
{ok, HrlFd} = file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]),
[F(F, HrlFd), F(F, Fd)];
{ok, {attribute, _, include_lib, Hrl}, _} ->
case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ -> [F(F, Fd)]
end;
{ok, {attribute, _, import, {Imp, _}}, _} ->
case file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(Imp) ++ ".erl", [read]) of
{ok, ImpFd} -> [Imp, F(F, ImpFd), F(F, Fd)];
_ -> [F(F, Fd)]
end;
{eof, _} ->
file:close(Fd),
[];
_ ->
F(F, Fd)
end
end,
fun() ->
ErlFiles = filelib:wildcard("$(DEPS_DIR)/$(1)/src/*.erl"),
First0 = lists:usort(lists:flatten([begin
{ok, Fd} = file:open(F, [read]),
FindFirst(FindFirst, Fd)
end || F <- ErlFiles])),
First = lists:flatten([begin
{ok, Fd} = file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", [read]),
FindFirst(FindFirst, Fd)
end || M <- First0, lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)]) ++ First0,
Write(["COMPILE_FIRST +=", [[" ", atom_to_list(M)] || M <- First,
lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)], "\n"])
end(),
Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app\n"),
Write("\npreprocess::\n"),
Write("\npre-deps::\n"),
Write("\npre-app::\n"),
PatchHook = fun(Cmd) ->
case Cmd of
"make -C" ++ Cmd1 -> "$$$$\(MAKE) -C" ++ Escape(Cmd1);
"gmake -C" ++ Cmd1 -> "$$$$\(MAKE) -C" ++ Escape(Cmd1);
"make " ++ Cmd1 -> "$$$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
"gmake " ++ Cmd1 -> "$$$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
_ -> Escape(Cmd)
end
end,
fun() ->
case lists:keyfind(pre_hooks, 1, Conf) of
false -> ok;
{_, Hooks} ->
[case H of
{'get-deps', Cmd} ->
Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n");
{compile, Cmd} ->
Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
{Regex, compile, Cmd} ->
case rebar_utils:is_arch(Regex) of
true -> Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
false -> ok
end;
_ -> ok
end || H <- Hooks]
end
end(),
ShellToMk = fun(V) ->
re:replace(re:replace(V, "(\\\\$$$$)(\\\\w*)", "\\\\1(\\\\2)", [global]),
"-Werror\\\\b", "", [{return, list}, global])
end,
PortSpecs = fun() ->
case lists:keyfind(port_specs, 1, Conf) of
false ->
case filelib:is_dir("$(DEPS_DIR)/$(1)/c_src") of
false -> [];
true ->
[{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"),
proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}]
end;
{_, Specs} ->
lists:flatten([case S of
{Output, Input} -> {ShellToMk(Output), Input, []};
{Regex, Output, Input} ->
case rebar_utils:is_arch(Regex) of
true -> {ShellToMk(Output), Input, []};
false -> []
end;
{Regex, Output, Input, [{env, Env}]} ->
case rebar_utils:is_arch(Regex) of
true -> {ShellToMk(Output), Input, Env};
false -> []
end
end || S <- Specs])
end
end(),
PortSpecWrite = fun (Text) ->
file:write_file("$(DEPS_DIR)/$(1)/c_src/Makefile.erlang.mk", Text, [append])
end,
case PortSpecs of
[] -> ok;
_ ->
Write("\npre-app::\n\t$$$$\(MAKE) -f c_src/Makefile.erlang.mk\n"),
PortSpecWrite(io_lib:format("ERL_CFLAGS = -finline-functions -Wall -fPIC -I ~s/erts-~s/include -I ~s\n",
[code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])),
PortSpecWrite(io_lib:format("ERL_LDFLAGS = -L ~s -lerl_interface -lei\n",
[code:lib_dir(erl_interface, lib)])),
[PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv],
FilterEnv = fun(Env) ->
lists:flatten([case E of
{_, _} -> E;
{Regex, K, V} ->
case rebar_utils:is_arch(Regex) of
true -> {K, V};
false -> []
end
end || E <- Env])
end,
MergeEnv = fun(Env) ->
lists:foldl(fun ({K, V}, Acc) ->
case lists:keyfind(K, 1, Acc) of
false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc];
{_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc]
end
end, [], Env)
end,
PortEnv = case lists:keyfind(port_env, 1, Conf) of
false -> [];
{_, PortEnv0} -> FilterEnv(PortEnv0)
end,
PortSpec = fun ({Output, Input0, Env}) ->
filelib:ensure_dir("$(DEPS_DIR)/$(1)/" ++ Output),
Input = [[" ", I] || I <- Input0],
PortSpecWrite([
[["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))],
case $(PLATFORM) of
darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress";
_ -> ""
end,
"\n\nall:: ", Output, "\n\n",
"%.o: %.c\n\t$$$$\(CC) -c -o $$$$\@ $$$$\< $$$$\(CFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.C\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.cc\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.cpp\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
[[Output, ": ", K, " = ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))],
Output, ": $$$$\(foreach ext,.c .C .cc .cpp,",
"$$$$\(patsubst %$$$$\(ext),%.o,$$$$\(filter %$$$$\(ext),$$$$\(wildcard", Input, "))))\n",
"\t$$$$\(CC) -o $$$$\@ $$$$\? $$$$\(LDFLAGS) $$$$\(ERL_LDFLAGS) $$$$\(DRV_LDFLAGS) $$$$\(EXE_LDFLAGS)",
case filename:extension(Output) of
[] -> "\n";
_ -> " -shared\n"
end])
end,
[PortSpec(S) || S <- PortSpecs]
end,
Write("\ninclude $(ERLANG_MK_FILENAME)"),
RunPlugin = fun(Plugin, Step) ->
case erlang:function_exported(Plugin, Step, 2) of
false -> ok;
true ->
c:cd("$(DEPS_DIR)/$(1)/"),
Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(),
dict:store(base_dir, "", dict:new())}, undefined),
io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret])
end
end,
fun() ->
case lists:keyfind(plugins, 1, Conf) of
false -> ok;
{_, Plugins} ->
[begin
case lists:keyfind(deps, 1, Conf) of
false -> ok;
{_, Deps} ->
case lists:keyfind(P, 1, Deps) of
false -> ok;
_ ->
Path = "$(DEPS_DIR)/" ++ atom_to_list(P),
io:format("~s", [os:cmd("$(MAKE) -C $(DEPS_DIR)/$(1) " ++ Path)]),
io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]),
code:add_patha(Path ++ "/ebin")
end
end
end || P <- Plugins],
[case code:load_file(P) of
{module, P} -> ok;
_ ->
case lists:keyfind(plugin_dir, 1, Conf) of
false -> ok;
{_, PluginsDir} ->
ErlFile = "$(DEPS_DIR)/$(1)/" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl",
{ok, P, Bin} = compile:file(ErlFile, [binary]),
{module, P} = code:load_binary(P, ErlFile, Bin)
end
end || P <- Plugins],
[RunPlugin(P, preprocess) || P <- Plugins],
[RunPlugin(P, pre_compile) || P <- Plugins]
end
end(),
halt()
endef
define dep_autopatch_app.erl
UpdateModules = fun(App) ->
case filelib:is_regular(App) of
false -> ok;
true ->
{ok, [{application, $(1), L0}]} = file:consult(App),
Mods = filelib:fold_files("$(DEPS_DIR)/$(1)/src", "\\\\.erl$$$$", true,
fun (F, Acc) -> [list_to_atom(filename:rootname(filename:basename(F)))|Acc] end, []),
L = lists:keystore(modules, 1, L0, {modules, Mods}),
ok = file:write_file(App, io_lib:format("~p.~n", [{application, $(1), L}]))
end
end,
UpdateModules("$(DEPS_DIR)/$(1)/ebin/$(1).app"),
halt()
endef
define dep_autopatch_appsrc.erl
AppSrcOut = "$(DEPS_DIR)/$(1)/src/$(1).app.src",
AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(DEPS_DIR)/$(1)/ebin/$(1).app"; true -> AppSrcOut end,
case filelib:is_regular(AppSrcIn) of
false -> ok;
true ->
{ok, [{application, $(1), L0}]} = file:consult(AppSrcIn),
L1 = lists:keystore(modules, 1, L0, {modules, []}),
L2 = case lists:keyfind(vsn, 1, L1) of {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, "git"}); _ -> L1 end,
L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end,
ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L3}])),
case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end
end,
halt()
endef
define dep_fetch_git
git clone -q -n -- $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \
cd $(DEPS_DIR)/$(call dep_name,$(1)) && git checkout -q $(call dep_commit,$(1));
endef
define dep_fetch_hg
hg clone -q -U $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \
cd $(DEPS_DIR)/$(call dep_name,$(1)) && hg update -q $(call dep_commit,$(1));
endef
define dep_fetch_svn
svn checkout -q $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
endef
define dep_fetch_cp
cp -R $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
endef
define dep_fetch_hex.erl
ssl:start(),
inets:start(),
{ok, {{_, 200, _}, _, Body}} = httpc:request(get,
{"https://s3.amazonaws.com/s3.hex.pm/tarballs/$(1)-$(2).tar", []},
[], [{body_format, binary}]),
{ok, Files} = erl_tar:extract({binary, Body}, [memory]),
{_, Source} = lists:keyfind("contents.tar.gz", 1, Files),
ok = erl_tar:extract({binary, Source}, [{cwd, "$(DEPS_DIR)/$(1)"}, compressed]),
halt()
endef
# Hex only has a package version. No need to look in the Erlang.mk packages.
define dep_fetch_hex
$(call erlang,$(call dep_fetch_hex.erl,$(1),$(strip $(word 2,$(dep_$(1))))));
endef
define dep_fetch_fail
echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \
exit 78;
endef
# Kept for compatibility purposes with older Erlang.mk configuration.
define dep_fetch_legacy
$(warning WARNING: '$(1)' dependency configuration uses deprecated format.) \
git clone -q -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1); \
cd $(DEPS_DIR)/$(1) && git checkout -q $(if $(word 2,$(dep_$(1))),$(word 2,$(dep_$(1))),master);
endef
define dep_fetch
$(if $(dep_$(1)), \
$(if $(dep_fetch_$(word 1,$(dep_$(1)))), \
$(word 1,$(dep_$(1))), \
legacy), \
$(if $(filter $(1),$(PACKAGES)), \
$(pkg_$(1)_fetch), \
fail))
endef
dep_name = $(if $(dep_$(1)),$(1),$(pkg_$(1)_name))
dep_repo = $(patsubst git://github.com/%,https://github.com/%, \
$(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_repo)))
dep_commit = $(if $(dep_$(1)),$(word 3,$(dep_$(1))),$(pkg_$(1)_commit))
define dep_target
$(DEPS_DIR)/$(1):
$(verbose) mkdir -p $(DEPS_DIR)
$(dep_verbose) $(call dep_fetch_$(strip $(call dep_fetch,$(1))),$(1))
$(verbose) if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ]; then \
echo " AUTO " $(1); \
cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \
fi
- $(verbose) if [ -f $(DEPS_DIR)/$(1)/configure ]; then \
echo " CONF " $(1); \
cd $(DEPS_DIR)/$(1) && ./configure; \
fi
ifeq ($(filter $(1),$(NO_AUTOPATCH)),)
$(verbose) if [ "$(1)" = "amqp_client" -a "$(RABBITMQ_CLIENT_PATCH)" ]; then \
if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
echo " PATCH Downloading rabbitmq-codegen"; \
git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
fi; \
if [ ! -d $(DEPS_DIR)/rabbitmq-server ]; then \
echo " PATCH Downloading rabbitmq-server"; \
git clone https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server; \
fi; \
ln -s $(DEPS_DIR)/amqp_client/deps/rabbit_common-0.0.0 $(DEPS_DIR)/rabbit_common; \
elif [ "$(1)" = "rabbit" -a "$(RABBITMQ_SERVER_PATCH)" ]; then \
if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
echo " PATCH Downloading rabbitmq-codegen"; \
git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
fi \
else \
$(call dep_autopatch,$(1)) \
fi
endif
endef
$(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep))))
distclean-deps:
$(gen_verbose) rm -rf $(DEPS_DIR)
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
# Verbosity.
proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F));
proto_verbose = $(proto_verbose_$(V))
# Core targets.
define compile_proto
$(verbose) mkdir -p ebin/ include/
$(proto_verbose) $(call erlang,$(call compile_proto.erl,$(1)))
$(proto_verbose) erlc +debug_info -o ebin/ ebin/*.erl
$(verbose) rm ebin/*.erl
endef
define compile_proto.erl
[begin
Dir = filename:dirname(filename:dirname(F)),
protobuffs_compile:generate_source(F,
[{output_include_dir, Dir ++ "/include"},
{output_src_dir, Dir ++ "/ebin"}])
end || F <- string:tokens("$(1)", " ")],
halt().
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.proto))
$(if $(strip $?),$(call compile_proto,$?))
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: clean-app
# Configuration.
ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
+warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
COMPILE_FIRST ?=
COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
ERLC_EXCLUDE ?=
ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
ERLC_MIB_OPTS ?=
COMPILE_MIB_FIRST ?=
COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
# Verbosity.
app_verbose_0 = @echo " APP " $(PROJECT);
app_verbose = $(app_verbose_$(V))
appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
appsrc_verbose = $(appsrc_verbose_$(V))
erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
$(filter %.erl %.core,$(?F)));
erlc_verbose = $(erlc_verbose_$(V))
xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
xyrl_verbose = $(xyrl_verbose_$(V))
asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F));
asn1_verbose = $(asn1_verbose_$(V))
mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
mib_verbose = $(mib_verbose_$(V))
# Targets.
ifeq ($(wildcard ebin/test),)
app:: app-build
else
app:: clean app-build
endif
ifeq ($(wildcard src/$(PROJECT)_app.erl),)
define app_file
{application, $(PROJECT), [
{description, "$(PROJECT_DESCRIPTION)"},
{vsn, "$(PROJECT_VERSION)"},
{id, "$(1)"},
{modules, [$(call comma_list,$(2))]},
{registered, []},
{applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(DEPS))]}
]}.
endef
else
define app_file
{application, $(PROJECT), [
{description, "$(PROJECT_DESCRIPTION)"},
{vsn, "$(PROJECT_VERSION)"},
{id, "$(1)"},
{modules, [$(call comma_list,$(2))]},
{registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]},
{applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(DEPS))]},
{mod, {$(PROJECT)_app, []}}
]}.
endef
endif
app-build: erlc-include ebin/$(PROJECT).app
$(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true))
$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(shell find ebin -type f -name *.beam))))))
ifeq ($(wildcard src/$(PROJECT).app.src),)
$(app_verbose) echo $(subst $(newline),,$(subst ",\",$(call app_file,$(GITDESCRIBE),$(MODULES)))) \
> ebin/$(PROJECT).app
else
$(verbose) if [ -z "$$(grep -E '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \
echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \
exit 1; \
fi
$(appsrc_verbose) cat src/$(PROJECT).app.src \
| sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \
| sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(GITDESCRIBE)\"}/" \
> ebin/$(PROJECT).app
endif
erlc-include:
- $(verbose) if [ -d ebin/ ]; then \
find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \
fi
define compile_erl
$(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \
-pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),\
$(COMPILE_FIRST_PATHS) $(1))
endef
define compile_xyrl
$(xyrl_verbose) erlc -v -o ebin/ $(1)
$(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl
$(verbose) rm ebin/*.erl
endef
define compile_asn1
$(asn1_verbose) erlc -v -I include/ -o ebin/ $(1)
$(verbose) mv ebin/*.hrl include/
$(verbose) mv ebin/*.asn1db include/
$(verbose) rm ebin/*.erl
endef
define compile_mib
$(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ \
-I priv/mibs/ $(COMPILE_MIB_FIRST_PATHS) $(1)
$(mib_verbose) erlc -o include/ -- priv/mibs/*.bin
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app::
$(verbose) mkdir -p ebin/
ifneq ($(wildcard asn1/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,asn1/,*.asn1))
$(verbose) mkdir -p include
$(if $(strip $?),$(call compile_asn1,$?))
endif
ifneq ($(wildcard mibs/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,mibs/,*.mib))
$(verbose) mkdir -p priv/mibs/ include
$(if $(strip $?),$(call compile_mib,$?))
endif
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.erl *.core))
$(if $(strip $?),$(call compile_erl,$?))
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.xrl *.yrl))
$(if $(strip $?),$(call compile_xyrl,$?))
endif
clean:: clean-app
clean-app:
$(gen_verbose) rm -rf ebin/ priv/mibs/ \
$(addprefix include/,$(addsuffix .hrl,$(notdir $(basename $(call core_find,mibs/,*.mib)))))
# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: docs-deps
# Configuration.
ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))
# Targets.
$(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))
ifneq ($(SKIP_DEPS),)
doc-deps:
else
doc-deps: $(ALL_DOC_DEPS_DIRS)
$(verbose) for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: test-deps test-dir test-build clean-test-dir
# Configuration.
TEST_DIR ?= $(CURDIR)/test
ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
TEST_ERLC_OPTS += -DTEST=1
# Targets.
$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
ifneq ($(SKIP_DEPS),)
test-deps:
else
test-deps: $(ALL_TEST_DEPS_DIRS)
$(verbose) for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done
endif
ifneq ($(wildcard $(TEST_DIR)),)
test-dir:
$(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o $(TEST_DIR) \
$(call core_find,$(TEST_DIR)/,*.erl) -pa ebin/
endif
ifeq ($(wildcard ebin/test),)
test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build:: clean deps test-deps
$(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
$(gen_verbose) touch ebin/test
else
test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build:: deps test-deps
$(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
endif
clean:: clean-test-dir
clean-test-dir:
ifneq ($(wildcard $(TEST_DIR)/*.beam),)
$(gen_verbose) rm -f $(TEST_DIR)/*.beam
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc
MAN_INSTALL_PATH ?= /usr/local/share/man
MAN_SECTIONS ?= 3 7
docs:: asciidoc
asciidoc: distclean-asciidoc doc-deps asciidoc-guide asciidoc-manual
ifeq ($(wildcard doc/src/guide/book.asciidoc),)
asciidoc-guide:
else
asciidoc-guide:
a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf
a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/
endif
ifeq ($(wildcard doc/src/manual/*.asciidoc),)
asciidoc-manual:
else
asciidoc-manual:
for f in doc/src/manual/*.asciidoc ; do \
a2x -v -f manpage $$f ; \
done
for s in $(MAN_SECTIONS); do \
mkdir -p doc/man$$s/ ; \
mv doc/src/manual/*.$$s doc/man$$s/ ; \
gzip doc/man$$s/*.$$s ; \
done
install-docs:: install-asciidoc
install-asciidoc: asciidoc-manual
for s in $(MAN_SECTIONS); do \
mkdir -p $(MAN_INSTALL_PATH)/man$$s/ ; \
install -g 0 -o 0 -m 0644 doc/man$$s/*.gz $(MAN_INSTALL_PATH)/man$$s/ ; \
done
endif
distclean:: distclean-asciidoc
distclean-asciidoc:
$(gen_verbose) rm -rf doc/html/ doc/guide.pdf doc/man3/ doc/man7/
# Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Bootstrap targets:" \
" bootstrap Generate a skeleton of an OTP application" \
" bootstrap-lib Generate a skeleton of an OTP library" \
" bootstrap-rel Generate the files needed to build a release" \
" new t=TPL n=NAME Generate a module NAME based on the template TPL" \
" list-templates List available templates"
# Bootstrap templates.
define bs_appsrc
{application, $(PROJECT), [
{description, ""},
{vsn, "0.1.0"},
{id, "git"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, {$(PROJECT)_app, []}},
{env, []}
]}.
endef
define bs_appsrc_lib
{application, $(PROJECT), [
{description, ""},
{vsn, "0.1.0"},
{id, "git"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]}
]}.
endef
ifdef SP
define bs_Makefile
PROJECT = $(PROJECT)
# Whitespace to be used when creating files from templates.
SP = $(SP)
include erlang.mk
endef
else
define bs_Makefile
PROJECT = $(PROJECT)
include erlang.mk
endef
endif
define bs_app
-module($(PROJECT)_app).
-behaviour(application).
-export([start/2]).
-export([stop/1]).
start(_Type, _Args) ->
$(PROJECT)_sup:start_link().
stop(_State) ->
ok.
endef
define bs_relx_config
{release, {$(PROJECT)_release, "1"}, [$(PROJECT)]}.
{extended_start_script, true}.
{sys_config, "rel/sys.config"}.
{vm_args, "rel/vm.args"}.
endef
define bs_sys_config
[
].
endef
define bs_vm_args
-name $(PROJECT)@127.0.0.1
-setcookie $(PROJECT)
-heart
endef
# Normal templates.
define tpl_supervisor
-module($(n)).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Procs = [],
{ok, {{one_for_one, 1, 5}, Procs}}.
endef
define tpl_gen_server
-module($(n)).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link(?MODULE, [], []).
%% gen_server.
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
endef
define tpl_cowboy_http
-module($(n)).
-behaviour(cowboy_http_handler).
-export([init/3]).
-export([handle/2]).
-export([terminate/3]).
-record(state, {
}).
init(_, Req, _Opts) ->
{ok, Req, #state{}}.
handle(Req, State=#state{}) ->
{ok, Req2} = cowboy_req:reply(200, Req),
{ok, Req2, State}.
terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_gen_fsm
-module($(n)).
-behaviour(gen_fsm).
%% API.
-export([start_link/0]).
%% gen_fsm.
-export([init/1]).
-export([state_name/2]).
-export([handle_event/3]).
-export([state_name/3]).
-export([handle_sync_event/4]).
-export([handle_info/3]).
-export([terminate/3]).
-export([code_change/4]).
-record(state, {
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_fsm:start_link(?MODULE, [], []).
%% gen_fsm.
init([]) ->
{ok, state_name, #state{}}.
state_name(_Event, StateData) ->
{next_state, state_name, StateData}.
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
state_name(_Event, _From, StateData) ->
{reply, ignored, state_name, StateData}.
handle_sync_event(_Event, _From, StateName, StateData) ->
{reply, ignored, StateName, StateData}.
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
terminate(_Reason, _StateName, _StateData) ->
ok.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
endef
define tpl_cowboy_loop
-module($(n)).
-behaviour(cowboy_loop_handler).
-export([init/3]).
-export([info/3]).
-export([terminate/3]).
-record(state, {
}).
init(_, Req, _Opts) ->
{loop, Req, #state{}, 5000, hibernate}.
info(_Info, Req, State) ->
{loop, Req, State, hibernate}.
terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_cowboy_rest
-module($(n)).
-export([init/3]).
-export([content_types_provided/2]).
-export([get_html/2]).
init(_, _Req, _Opts) ->
{upgrade, protocol, cowboy_rest}.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}.
get_html(Req, State) ->
{<<"<html><body>This is REST!</body></html>">>, Req, State}.
endef
define tpl_cowboy_ws
-module($(n)).
-behaviour(cowboy_websocket_handler).
-export([init/3]).
-export([websocket_init/3]).
-export([websocket_handle/3]).
-export([websocket_info/3]).
-export([websocket_terminate/3]).
-record(state, {
}).
init(_, _, _) ->
{upgrade, protocol, cowboy_websocket}.
websocket_init(_, Req, _Opts) ->
Req2 = cowboy_req:compact(Req),
{ok, Req2, #state{}}.
websocket_handle({text, Data}, Req, State) ->
{reply, {text, Data}, Req, State};
websocket_handle({binary, Data}, Req, State) ->
{reply, {binary, Data}, Req, State};
websocket_handle(_Frame, Req, State) ->
{ok, Req, State}.
websocket_info(_Info, Req, State) ->
{ok, Req, State}.
websocket_terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_ranch_protocol
-module($(n)).
-behaviour(ranch_protocol).
-export([start_link/4]).
-export([init/4]).
-type opts() :: [].
-export_type([opts/0]).
-record(state, {
socket :: inet:socket(),
transport :: module()
}).
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.
-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, _Opts) ->
ok = ranch:accept_ack(Ref),
loop(#state{socket=Socket, transport=Transport}).
loop(State) ->
loop(State).
endef
# Plugin-specific targets.
define render_template
$(verbose) echo "$${_$(1)}" > $(2)
endef
ifndef WS
ifdef SP
WS = $(subst a,,a $(wordlist 1,$(SP),a a a a a a a a a a a a a a a a a a a a))
else
WS = $(tab)
endif
endif
$(foreach template,$(filter bs_% tpl_%,$(.VARIABLES)), \
$(eval _$(template) = $$(subst $$(tab),$$(WS),$$($(template)))) \
$(eval export _$(template)))
bootstrap:
ifneq ($(wildcard src/),)
$(error Error: src/ directory already exists)
endif
$(call render_template,bs_Makefile,Makefile)
$(verbose) mkdir src/
$(call render_template,bs_appsrc,src/$(PROJECT).app.src)
$(call render_template,bs_app,src/$(PROJECT)_app.erl)
$(eval n := $(PROJECT)_sup)
$(call render_template,tpl_supervisor,src/$(PROJECT)_sup.erl)
bootstrap-lib:
ifneq ($(wildcard src/),)
$(error Error: src/ directory already exists)
endif
$(call render_template,bs_Makefile,Makefile)
$(verbose) mkdir src/
$(call render_template,bs_appsrc_lib,src/$(PROJECT).app.src)
bootstrap-rel:
ifneq ($(wildcard relx.config),)
$(error Error: relx.config already exists)
endif
ifneq ($(wildcard rel/),)
$(error Error: rel/ directory already exists)
endif
$(call render_template,bs_relx_config,relx.config)
$(verbose) mkdir rel/
$(call render_template,bs_sys_config,rel/sys.config)
$(call render_template,bs_vm_args,rel/vm.args)
new:
ifeq ($(wildcard src/),)
$(error Error: src/ directory does not exist)
endif
ifndef t
$(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
endif
ifndef tpl_$(t)
$(error Unknown template)
endif
ifndef n
$(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
endif
$(call render_template,tpl_$(t),src/$(n).erl)
list-templates:
$(verbose) echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))
# Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: clean-c_src distclean-c_src-env
# Configuration.
C_SRC_DIR ?= $(CURDIR)/c_src
C_SRC_ENV ?= $(C_SRC_DIR)/env.mk
C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT).so
C_SRC_TYPE ?= shared
# System type and C compiler/flags.
ifeq ($(PLATFORM),darwin)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall
LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress
else ifeq ($(PLATFORM),freebsd)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
else ifeq ($(PLATFORM),linux)
CC ?= gcc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
endif
CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei
ifeq ($(C_SRC_TYPE),shared)
LDFLAGS += -shared
endif
# Verbosity.
c_verbose_0 = @echo " C " $(?F);
c_verbose = $(c_verbose_$(V))
cpp_verbose_0 = @echo " CPP " $(?F);
cpp_verbose = $(cpp_verbose_$(V))
link_verbose_0 = @echo " LD " $(@F);
link_verbose = $(link_verbose_$(V))
# Targets.
ifeq ($(wildcard $(C_SRC_DIR)),)
else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),)
app:: app-c_src
test-build:: app-c_src
app-c_src:
$(MAKE) -C $(C_SRC_DIR)
clean::
$(MAKE) -C $(C_SRC_DIR) clean
else
ifeq ($(SOURCES),)
SOURCES := $(sort $(call core_find,$(C_SRC_DIR)/,*.c *.C *.cc *.cpp))
endif
OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
app:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
$(C_SRC_OUTPUT): $(OBJECTS)
$(verbose) mkdir -p priv/
$(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT)
%.o: %.c
$(COMPILE_C) $(OUTPUT_OPTION) $<
%.o: %.cc
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.C
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.cpp
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
clean:: clean-c_src
clean-c_src:
$(gen_verbose) rm -f $(C_SRC_OUTPUT) $(OBJECTS)
endif
ifneq ($(wildcard $(C_SRC_DIR)),)
$(C_SRC_ENV):
$(verbose) $(ERL) -eval "file:write_file(\"$(C_SRC_ENV)\", \
io_lib:format( \
\"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \
\"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \
\"ERL_INTERFACE_LIB_DIR ?= ~s~n\", \
[code:root_dir(), erlang:system_info(version), \
code:lib_dir(erl_interface, include), \
code:lib_dir(erl_interface, lib)])), \
halt()."
distclean:: distclean-c_src-env
distclean-c_src-env:
$(gen_verbose) rm -f $(C_SRC_ENV)
-include $(C_SRC_ENV)
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: ci ci-setup distclean-kerl
KERL ?= $(CURDIR)/kerl
export KERL
KERL_URL ?= https://raw.githubusercontent.com/yrashk/kerl/master/kerl
OTP_GIT ?= https://github.com/erlang/otp
CI_INSTALL_DIR ?= $(HOME)/erlang
CI_OTP ?=
ifeq ($(strip $(CI_OTP)),)
ci::
else
ci:: $(addprefix ci-,$(CI_OTP))
ci-prepare: $(addprefix $(CI_INSTALL_DIR)/,$(CI_OTP))
ci-setup::
ci_verbose_0 = @echo " CI " $(1);
ci_verbose = $(ci_verbose_$(V))
define ci_target
ci-$(1): $(CI_INSTALL_DIR)/$(1)
$(ci_verbose) \
PATH="$(CI_INSTALL_DIR)/$(1)/bin:$(PATH)" \
CI_OTP_RELEASE="$(1)" \
CT_OPTS="-label $(1)" \
$(MAKE) clean ci-setup tests
endef
$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp))))
define ci_otp_target
ifeq ($(wildcard $(CI_INSTALL_DIR)/$(1)),)
$(CI_INSTALL_DIR)/$(1): $(KERL)
$(KERL) build git $(OTP_GIT) $(1) $(1)
$(KERL) install $(1) $(CI_INSTALL_DIR)/$(1)
endif
endef
$(foreach otp,$(CI_OTP),$(eval $(call ci_otp_target,$(otp))))
$(KERL):
$(gen_verbose) $(call core_http_get,$(KERL),$(KERL_URL))
$(verbose) chmod +x $(KERL)
help::
$(verbose) printf "%s\n" "" \
"Continuous Integration targets:" \
" ci Run '$(MAKE) tests' on all configured Erlang versions." \
"" \
"The CI_OTP variable must be defined with the Erlang versions" \
"that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3"
distclean:: distclean-kerl
distclean-kerl:
$(gen_verbose) rm -rf $(KERL)
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: ct distclean-ct
# Configuration.
CT_OPTS ?=
ifneq ($(wildcard $(TEST_DIR)),)
CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl))))
else
CT_SUITES ?=
endif
# Core targets.
tests:: ct
distclean:: distclean-ct
help::
$(verbose) printf "%s\n" "" \
"Common_test targets:" \
" ct Run all the common_test suites for this project" \
"" \
"All your common_test suites have their associated targets." \
"A suite named http_SUITE can be ran using the ct-http target."
# Plugin-specific targets.
CT_RUN = ct_run \
-no_auto_compile \
-noinput \
-pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin $(TEST_DIR) \
-dir $(TEST_DIR) \
-logdir $(CURDIR)/logs
ifeq ($(CT_SUITES),)
ct:
else
ct: test-build
$(verbose) mkdir -p $(CURDIR)/logs/
$(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS)
endif
define ct_suite_target
ct-$(1): test-build
$(verbose) mkdir -p $(CURDIR)/logs/
$(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS)
endef
$(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test))))
distclean-ct:
$(gen_verbose) rm -rf $(CURDIR)/logs/
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: plt distclean-plt dialyze
# Configuration.
DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt
export DIALYZER_PLT
PLT_APPS ?=
DIALYZER_DIRS ?= --src -r src
DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \
-Wunmatched_returns # -Wunderspecs
# Core targets.
check:: dialyze
distclean:: distclean-plt
help::
$(verbose) printf "%s\n" "" \
"Dialyzer targets:" \
" plt Build a PLT file for this project" \
" dialyze Analyze the project using Dialyzer"
# Plugin-specific targets.
$(DIALYZER_PLT): deps app
$(verbose) dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(ALL_DEPS_DIRS)
plt: $(DIALYZER_PLT)
distclean-plt:
$(gen_verbose) rm -f $(DIALYZER_PLT)
ifneq ($(wildcard $(DIALYZER_PLT)),)
dialyze:
else
dialyze: $(DIALYZER_PLT)
endif
$(verbose) dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-edoc edoc
# Configuration.
EDOC_OPTS ?=
# Core targets.
docs:: distclean-edoc edoc
distclean:: distclean-edoc
# Plugin-specific targets.
edoc: doc-deps
$(gen_verbose) $(ERL) -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), halt().'
distclean-edoc:
$(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
# Copyright (c) 2015, Erlang Solutions Ltd.
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: elvis distclean-elvis
# Configuration.
ELVIS_CONFIG ?= $(CURDIR)/elvis.config
ELVIS ?= $(CURDIR)/elvis
export ELVIS
ELVIS_URL ?= https://github.com/inaka/elvis/releases/download/0.2.5-beta2/elvis
ELVIS_CONFIG_URL ?= https://github.com/inaka/elvis/releases/download/0.2.5-beta2/elvis.config
ELVIS_OPTS ?=
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Elvis targets:" \
" elvis Run Elvis using the local elvis.config or download the default otherwise"
distclean:: distclean-elvis
# Plugin-specific targets.
$(ELVIS):
$(gen_verbose) $(call core_http_get,$(ELVIS),$(ELVIS_URL))
$(verbose) chmod +x $(ELVIS)
$(ELVIS_CONFIG):
$(verbose) $(call core_http_get,$(ELVIS_CONFIG),$(ELVIS_CONFIG_URL))
elvis: $(ELVIS) $(ELVIS_CONFIG)
$(verbose) $(ELVIS) rock -c $(ELVIS_CONFIG) $(ELVIS_OPTS)
distclean-elvis:
$(gen_verbose) rm -rf $(ELVIS)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
# Configuration.
DTL_FULL_PATH ?= 0
DTL_PATH ?= templates/
DTL_SUFFIX ?= _dtl
# Verbosity.
dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
dtl_verbose = $(dtl_verbose_$(V))
# Core targets.
define erlydtl_compile.erl
[begin
Module0 = case $(DTL_FULL_PATH) of
0 ->
filename:basename(F, ".dtl");
1 ->
"$(DTL_PATH)" ++ F2 = filename:rootname(F, ".dtl"),
re:replace(F2, "/", "_", [{return, list}, global])
end,
Module = list_to_atom(string:to_lower(Module0) ++ "$(DTL_SUFFIX)"),
case erlydtl:compile(F, Module, [{out_dir, "ebin/"}, return_errors, {doc_root, "templates"}]) of
ok -> ok;
{ok, _} -> ok
end
end || F <- string:tokens("$(1)", " ")],
halt().
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,$(DTL_PATH),*.dtl))
$(if $(strip $?),\
$(dtl_verbose) $(call erlang,$(call erlydtl_compile.erl,$?,-pa ebin/ $(DEPS_DIR)/erlydtl/ebin/)))
endif
# Copyright (c) 2014 Dave Cottlehuber <dch@skunkwerks.at>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-escript escript
# Configuration.
ESCRIPT_NAME ?= $(PROJECT)
ESCRIPT_COMMENT ?= This is an -*- erlang -*- file
ESCRIPT_BEAMS ?= "ebin/*", "deps/*/ebin/*"
ESCRIPT_SYS_CONFIG ?= "rel/sys.config"
ESCRIPT_EMU_ARGS ?= -pa . \
-sasl errlog_type error \
-escript main $(ESCRIPT_NAME)
ESCRIPT_SHEBANG ?= /usr/bin/env escript
ESCRIPT_STATIC ?= "deps/*/priv/**", "priv/**"
# Core targets.
distclean:: distclean-escript
help::
$(verbose) printf "%s\n" "" \
"Escript targets:" \
" escript Build an executable escript archive" \
# Plugin-specific targets.
# Based on https://github.com/synrc/mad/blob/master/src/mad_bundle.erl
# Copyright (c) 2013 Maxim Sokhatsky, Synrc Research Center
# Modified MIT License, https://github.com/synrc/mad/blob/master/LICENSE :
# Software may only be used for the great good and the true happiness of all
# sentient beings.
define ESCRIPT_RAW
'Read = fun(F) -> {ok, B} = file:read_file(filename:absname(F)), B end,'\
'Files = fun(L) -> A = lists:concat([filelib:wildcard(X)||X<- L ]),'\
' [F || F <- A, not filelib:is_dir(F) ] end,'\
'Squash = fun(L) -> [{filename:basename(F), Read(F) } || F <- L ] end,'\
'Zip = fun(A, L) -> {ok,{_,Z}} = zip:create(A, L, [{compress,all},memory]), Z end,'\
'Ez = fun(Escript) ->'\
' Static = Files([$(ESCRIPT_STATIC)]),'\
' Beams = Squash(Files([$(ESCRIPT_BEAMS), $(ESCRIPT_SYS_CONFIG)])),'\
' Archive = Beams ++ [{ "static.gz", Zip("static.gz", Static)}],'\
' escript:create(Escript, [ $(ESCRIPT_OPTIONS)'\
' {archive, Archive, [memory]},'\
' {shebang, "$(ESCRIPT_SHEBANG)"},'\
' {comment, "$(ESCRIPT_COMMENT)"},'\
' {emu_args, " $(ESCRIPT_EMU_ARGS)"}'\
' ]),'\
' file:change_mode(Escript, 8#755)'\
'end,'\
'Ez("$(ESCRIPT_NAME)"),'\
'halt().'
endef
ESCRIPT_COMMAND = $(subst ' ',,$(ESCRIPT_RAW))
escript:: distclean-escript deps app
$(gen_verbose) $(ERL) -eval $(ESCRIPT_COMMAND)
distclean-escript:
$(gen_verbose) rm -f $(ESCRIPT_NAME)
# Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is contributed to erlang.mk and subject to the terms of the ISC License.
.PHONY: eunit
# Configuration
EUNIT_OPTS ?=
# Core targets.
tests:: eunit
help::
$(verbose) printf "%s\n" "" \
"EUnit targets:" \
" eunit Run all the EUnit tests for this project"
# Plugin-specific targets.
define eunit.erl
case "$(COVER)" of
"" -> ok;
_ ->
case cover:compile_beam_directory("ebin") of
{error, _} -> halt(1);
_ -> ok
end
end,
case eunit:test([$(call comma_list,$(1))], [$(EUNIT_OPTS)]) of
ok -> ok;
error -> halt(2)
end,
case "$(COVER)" of
"" -> ok;
_ ->
cover:export("eunit.coverdata")
end,
halt()
endef
EUNIT_EBIN_MODS = $(notdir $(basename $(call core_find,ebin/,*.beam)))
EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.beam)))
EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \
$(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),{module,'$(mod)'})
eunit: test-build
$(gen_verbose) $(ERL) -pa $(TEST_DIR) $(DEPS_DIR)/*/ebin ebin \
-eval "$(subst $(newline),,$(subst ",\",$(call eunit.erl,$(EUNIT_MODS))))"
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: relx-rel distclean-relx-rel distclean-relx run
# Configuration.
RELX_CONFIG ?= $(CURDIR)/relx.config
RELX ?= $(CURDIR)/relx
export RELX
RELX_URL ?= https://github.com/erlware/relx/releases/download/v2.0.0/relx
RELX_OPTS ?=
RELX_OUTPUT_DIR ?= _rel
ifeq ($(firstword $(RELX_OPTS)),-o)
RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS))
else
RELX_OPTS += -o $(RELX_OUTPUT_DIR)
endif
# Core targets.
ifeq ($(IS_DEP),)
ifneq ($(wildcard $(RELX_CONFIG)),)
rel:: distclean-relx-rel relx-rel
endif
endif
distclean:: distclean-relx-rel distclean-relx
# Plugin-specific targets.
$(RELX):
$(gen_verbose) $(call core_http_get,$(RELX),$(RELX_URL))
$(verbose) chmod +x $(RELX)
relx-rel: $(RELX)
$(verbose) $(RELX) -c $(RELX_CONFIG) $(RELX_OPTS)
distclean-relx-rel:
$(gen_verbose) rm -rf $(RELX_OUTPUT_DIR)
distclean-relx:
$(gen_verbose) rm -rf $(RELX)
# Run target.
ifeq ($(wildcard $(RELX_CONFIG)),)
run:
else
define get_relx_release.erl
{ok, Config} = file:consult("$(RELX_CONFIG)"),
{release, {Name, _}, _} = lists:keyfind(release, 1, Config),
io:format("~s", [Name]),
halt(0).
endef
RELX_RELEASE = `$(call erlang,$(get_relx_release.erl))`
run: all
$(verbose) $(RELX_OUTPUT_DIR)/$(RELX_RELEASE)/bin/$(RELX_RELEASE) console
help::
$(verbose) printf "%s\n" "" \
"Relx targets:" \
" run Compile the project, build the release and run it"
endif
# Copyright (c) 2014, M Robert Martin <rob@version2beta.com>
# This file is contributed to erlang.mk and subject to the terms of the ISC License.
.PHONY: shell
# Configuration.
SHELL_PATH ?= -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin
SHELL_OPTS ?=
ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))
# Core targets
help::
$(verbose) printf "%s\n" "" \
"Shell targets:" \
" shell Run an erlang shell with SHELL_OPTS or reasonable default"
# Plugin-specific targets.
$(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep))))
build-shell-deps: $(ALL_SHELL_DEPS_DIRS)
$(verbose) for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done
shell: build-shell-deps
$(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS)
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
ifeq ($(filter triq,$(DEPS) $(TEST_DEPS)),triq)
.PHONY: triq
# Targets.
tests:: triq
define triq_check.erl
code:add_pathsa(["$(CURDIR)/ebin", "$(DEPS_DIR)/*/ebin"]),
try
case $(1) of
all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]);
module -> triq:check($(2));
function -> triq:check($(2))
end
of
true -> halt(0);
_ -> halt(1)
catch error:undef ->
io:format("Undefined property or module~n"),
halt(0)
end.
endef
ifdef t
ifeq (,$(findstring :,$(t)))
triq: test-build
$(verbose) $(call erlang,$(call triq_check.erl,module,$(t)))
else
triq: test-build
$(verbose) echo Testing $(t)/0
$(verbose) $(call erlang,$(call triq_check.erl,function,$(t)()))
endif
else
triq: test-build
$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam))))))
$(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES)))
endif
endif
# Copyright (c) 2015, Erlang Solutions Ltd.
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: xref distclean-xref
# Configuration.
ifeq ($(XREF_CONFIG),)
XREF_ARGS :=
else
XREF_ARGS := -c $(XREF_CONFIG)
endif
XREFR ?= $(CURDIR)/xrefr
export XREFR
XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/0.2.2/xrefr
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Xref targets:" \
" xref Run Xrefr using $XREF_CONFIG as config file if defined"
distclean:: distclean-xref
# Plugin-specific targets.
$(XREFR):
$(gen_verbose) $(call core_http_get,$(XREFR),$(XREFR_URL))
$(verbose) chmod +x $(XREFR)
xref: deps app $(XREFR)
$(gen_verbose) $(XREFR) $(XREFR_ARGS)
distclean-xref:
$(gen_verbose) rm -rf $(XREFR)
# Copyright 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
# This file is part of erlang.mk and subject to the terms of the ISC License.
COVER_REPORT_DIR = cover
# Hook in coverage to ct
ifdef COVER
ifdef CT_RUN
# All modules in 'ebin'
COVER_MODS = $(notdir $(basename $(call core_ls,ebin/*.beam)))
test-build:: $(TEST_DIR)/ct.cover.spec
$(TEST_DIR)/ct.cover.spec:
$(verbose) echo Cover mods: $(COVER_MODS)
$(gen_verbose) printf "%s\n" \
'{incl_mods,[$(subst $(space),$(comma),$(COVER_MODS))]}.' \
'{export,"$(CURDIR)/ct.coverdata"}.' > $@
CT_RUN += -cover $(TEST_DIR)/ct.cover.spec
endif
endif
# Core targets
ifdef COVER
ifneq ($(COVER_REPORT_DIR),)
tests::
$(verbose) $(MAKE) --no-print-directory cover-report
endif
endif
clean:: coverdata-clean
ifneq ($(COVER_REPORT_DIR),)
distclean:: cover-report-clean
endif
help::
$(verbose) printf "%s\n" "" \
"Cover targets:" \
" cover-report Generate a HTML coverage report from previously collected" \
" cover data." \
" all.coverdata Merge {eunit,ct}.coverdata into one coverdata file." \
"" \
"If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \
"target tests additionally generates a HTML coverage report from the combined" \
"coverdata files from each of these testing tools. HTML reports can be disabled" \
"by setting COVER_REPORT_DIR to empty."
# Plugin specific targets
COVERDATA = $(filter-out all.coverdata,$(wildcard *.coverdata))
.PHONY: coverdata-clean
coverdata-clean:
$(gen_verbose) rm -f *.coverdata ct.cover.spec
# Merge all coverdata files into one.
all.coverdata: $(COVERDATA)
$(gen_verbose) $(ERL) -eval ' \
$(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) \
cover:export("$@"), halt(0).'
# These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to
# empty if you want the coverdata files but not the HTML report.
ifneq ($(COVER_REPORT_DIR),)
.PHONY: cover-report-clean cover-report
cover-report-clean:
$(gen_verbose) rm -rf $(COVER_REPORT_DIR)
ifeq ($(COVERDATA),)
cover-report:
else
# Modules which include eunit.hrl always contain one line without coverage
# because eunit defines test/0 which is never called. We compensate for this.
EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \
grep -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \
| sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq))
define cover_report.erl
$(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
Ms = cover:imported_modules(),
[cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M)
++ ".COVER.html", [html]) || M <- Ms],
Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],
EunitHrlMods = [$(EUNIT_HRL_MODS)],
Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of
true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],
TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),
TotalN = lists:sum([N || {_, {_, N}} <- Report1]),
TotalPerc = round(100 * TotalY / (TotalY + TotalN)),
{ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]),
io:format(F, "<!DOCTYPE html><html>~n"
"<head><meta charset=\"UTF-8\">~n"
"<title>Coverage report</title></head>~n"
"<body>~n", []),
io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
[io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
"<td>~p%</td></tr>~n",
[M, M, round(100 * Y / (Y + N))]) || {M, {Y, N}} <- Report1],
How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))",
Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")",
io:format(F, "</table>~n"
"<p>Generated using ~s and erlang.mk on ~s.</p>~n"
"</body></html>", [How, Date]),
halt().
endef
cover-report:
$(gen_verbose) mkdir -p $(COVER_REPORT_DIR)
$(gen_verbose) $(call erlang,$(cover_report.erl))
endif
endif # ifneq ($(COVER_REPORT_DIR),)
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | %% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu> %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above %% copyright notice and this permission notice appear in all copies. %% %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -ifndef(COW_INLINE_HRL). -define(COW_INLINE_HRL, 1). %% INLINE_LOWERCASE(Function, Rest, Acc, ...) %% %% To be included at the end of a case block. %% Defined for up to 10 extra arguments. -define(INLINE_LOWERCASE(Function, Rest, Acc), $A -> Function(Rest, << Acc/binary, $a >>); $B -> Function(Rest, << Acc/binary, $b >>); $C -> Function(Rest, << Acc/binary, $c >>); $D -> Function(Rest, << Acc/binary, $d >>); $E -> Function(Rest, << Acc/binary, $e >>); $F -> Function(Rest, << Acc/binary, $f >>); $G -> Function(Rest, << Acc/binary, $g >>); $H -> Function(Rest, << Acc/binary, $h >>); $I -> Function(Rest, << Acc/binary, $i >>); $J -> Function(Rest, << Acc/binary, $j >>); $K -> Function(Rest, << Acc/binary, $k >>); $L -> Function(Rest, << Acc/binary, $l >>); $M -> Function(Rest, << Acc/binary, $m >>); $N -> Function(Rest, << Acc/binary, $n >>); $O -> Function(Rest, << Acc/binary, $o >>); $P -> Function(Rest, << Acc/binary, $p >>); $Q -> Function(Rest, << Acc/binary, $q >>); $R -> Function(Rest, << Acc/binary, $r >>); $S -> Function(Rest, << Acc/binary, $s >>); $T -> Function(Rest, << Acc/binary, $t >>); $U -> Function(Rest, << Acc/binary, $u >>); $V -> Function(Rest, << Acc/binary, $v >>); $W -> Function(Rest, << Acc/binary, $w >>); $X -> Function(Rest, << Acc/binary, $x >>); $Y -> Function(Rest, << Acc/binary, $y >>); $Z -> Function(Rest, << Acc/binary, $z >>); C -> Function(Rest, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, Acc), $A -> Function(Rest, A0, << Acc/binary, $a >>); $B -> Function(Rest, A0, << Acc/binary, $b >>); $C -> Function(Rest, A0, << Acc/binary, $c >>); $D -> Function(Rest, A0, << Acc/binary, $d >>); $E -> Function(Rest, A0, << Acc/binary, $e >>); $F -> Function(Rest, A0, << Acc/binary, $f >>); $G -> Function(Rest, A0, << Acc/binary, $g >>); $H -> Function(Rest, A0, << Acc/binary, $h >>); $I -> Function(Rest, A0, << Acc/binary, $i >>); $J -> Function(Rest, A0, << Acc/binary, $j >>); $K -> Function(Rest, A0, << Acc/binary, $k >>); $L -> Function(Rest, A0, << Acc/binary, $l >>); $M -> Function(Rest, A0, << Acc/binary, $m >>); $N -> Function(Rest, A0, << Acc/binary, $n >>); $O -> Function(Rest, A0, << Acc/binary, $o >>); $P -> Function(Rest, A0, << Acc/binary, $p >>); $Q -> Function(Rest, A0, << Acc/binary, $q >>); $R -> Function(Rest, A0, << Acc/binary, $r >>); $S -> Function(Rest, A0, << Acc/binary, $s >>); $T -> Function(Rest, A0, << Acc/binary, $t >>); $U -> Function(Rest, A0, << Acc/binary, $u >>); $V -> Function(Rest, A0, << Acc/binary, $v >>); $W -> Function(Rest, A0, << Acc/binary, $w >>); $X -> Function(Rest, A0, << Acc/binary, $x >>); $Y -> Function(Rest, A0, << Acc/binary, $y >>); $Z -> Function(Rest, A0, << Acc/binary, $z >>); C -> Function(Rest, A0, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, Acc), $A -> Function(Rest, A0, A1, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, Acc), $A -> Function(Rest, A0, A1, A2, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, Acc), $A -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, C >>) ). -define(INLINE_LOWERCASE(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, Acc), $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $a >>); $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $b >>); $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $c >>); $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $d >>); $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $e >>); $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $f >>); $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $g >>); $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $h >>); $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $i >>); $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $j >>); $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $k >>); $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $l >>); $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $m >>); $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $n >>); $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $o >>); $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $p >>); $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $q >>); $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $r >>); $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $s >>); $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $t >>); $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $u >>); $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $v >>); $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $w >>); $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $x >>); $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $y >>); $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $z >>); C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, C >>) ). %% INLINE_LOWERCASE_BC(Bin) %% %% Lowercase the entire binary string in a binary comprehension. -define(INLINE_LOWERCASE_BC(Bin), << << case C of $A -> $a; $B -> $b; $C -> $c; $D -> $d; $E -> $e; $F -> $f; $G -> $g; $H -> $h; $I -> $i; $J -> $j; $K -> $k; $L -> $l; $M -> $m; $N -> $n; $O -> $o; $P -> $p; $Q -> $q; $R -> $r; $S -> $s; $T -> $t; $U -> $u; $V -> $v; $W -> $w; $X -> $x; $Y -> $y; $Z -> $z; C -> C end >> || << C >> <= Bin >>). -endif. |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_cookie).
-export([parse_cookie/1]).
-export([setcookie/3]).
-type cookie_option() :: {max_age, non_neg_integer()}
| {domain, binary()} | {path, binary()}
| {secure, boolean()} | {http_only, boolean()}.
-type cookie_opts() :: [cookie_option()].
-export_type([cookie_opts/0]).
%% @doc Parse a cookie header string and return a list of key/values.
-spec parse_cookie(binary()) -> [{binary(), binary()}] | {error, badarg}.
parse_cookie(Cookie) ->
parse_cookie(Cookie, []).
parse_cookie(<<>>, Acc) ->
lists:reverse(Acc);
parse_cookie(<< $\s, Rest/binary >>, Acc) ->
parse_cookie(Rest, Acc);
parse_cookie(<< $\t, Rest/binary >>, Acc) ->
parse_cookie(Rest, Acc);
parse_cookie(<< $,, Rest/binary >>, Acc) ->
parse_cookie(Rest, Acc);
parse_cookie(<< $;, Rest/binary >>, Acc) ->
parse_cookie(Rest, Acc);
parse_cookie(<< $$, Rest/binary >>, Acc) ->
skip_cookie(Rest, Acc);
parse_cookie(Cookie, Acc) ->
parse_cookie_name(Cookie, Acc, <<>>).
skip_cookie(<<>>, Acc) ->
lists:reverse(Acc);
skip_cookie(<< $,, Rest/binary >>, Acc) ->
parse_cookie(Rest, Acc);
skip_cookie(<< $;, Rest/binary >>, Acc) ->
parse_cookie(Rest, Acc);
skip_cookie(<< _, Rest/binary >>, Acc) ->
skip_cookie(Rest, Acc).
parse_cookie_name(<<>>, _, _) ->
{error, badarg};
parse_cookie_name(<< $=, _/binary >>, _, <<>>) ->
{error, badarg};
parse_cookie_name(<< $=, Rest/binary >>, Acc, Name) ->
parse_cookie_value(Rest, Acc, Name, <<>>);
parse_cookie_name(<< $,, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< $;, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< $\s, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< $\t, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< $\r, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< $\n, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< $\013, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< $\014, _/binary >>, _, _) ->
{error, badarg};
parse_cookie_name(<< C, Rest/binary >>, Acc, Name) ->
parse_cookie_name(Rest, Acc, << Name/binary, C >>).
parse_cookie_value(<<>>, Acc, Name, Value) ->
lists:reverse([{Name, parse_cookie_trim(Value)}|Acc]);
parse_cookie_value(<< $;, Rest/binary >>, Acc, Name, Value) ->
parse_cookie(Rest, [{Name, parse_cookie_trim(Value)}|Acc]);
parse_cookie_value(<< $\t, _/binary >>, _, _, _) ->
{error, badarg};
parse_cookie_value(<< $\r, _/binary >>, _, _, _) ->
{error, badarg};
parse_cookie_value(<< $\n, _/binary >>, _, _, _) ->
{error, badarg};
parse_cookie_value(<< $\013, _/binary >>, _, _, _) ->
{error, badarg};
parse_cookie_value(<< $\014, _/binary >>, _, _, _) ->
{error, badarg};
parse_cookie_value(<< C, Rest/binary >>, Acc, Name, Value) ->
parse_cookie_value(Rest, Acc, Name, << Value/binary, C >>).
parse_cookie_trim(Value = <<>>) ->
Value;
parse_cookie_trim(Value) ->
case binary:last(Value) of
$\s ->
Size = byte_size(Value) - 1,
<< Value2:Size/binary, _ >> = Value,
parse_cookie_trim(Value2);
_ ->
Value
end.
-ifdef(TEST).
parse_cookie_test_() ->
%% {Value, Result}.
Tests = [
{<<"name=value; name2=value2">>, [
{<<"name">>, <<"value">>},
{<<"name2">>, <<"value2">>}
]},
{<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme">>, [
{<<"Customer">>, <<"WILE_E_COYOTE">>}
]},
{<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme; "
"Part_Number=Rocket_Launcher_0001; $Path=/acme; "
"Shipping=FedEx; $Path=/acme">>, [
{<<"Customer">>, <<"WILE_E_COYOTE">>},
{<<"Part_Number">>, <<"Rocket_Launcher_0001">>},
{<<"Shipping">>, <<"FedEx">>}
]},
%% Space in value.
{<<"foo=Thu Jul 11 2013 15:38:43 GMT+0400 (MSK)">>,
[{<<"foo">>, <<"Thu Jul 11 2013 15:38:43 GMT+0400 (MSK)">>}]},
%% Comma in value. Google Analytics sets that kind of cookies.
{<<"refk=sOUZDzq2w2; sk=B602064E0139D842D620C7569640DBB4C81C45080651"
"9CC124EF794863E10E80; __utma=64249653.825741573.1380181332.1400"
"015657.1400019557.703; __utmb=64249653.1.10.1400019557; __utmc="
"64249653; __utmz=64249653.1400019557.703.13.utmcsr=bluesky.chic"
"agotribune.com|utmccn=(referral)|utmcmd=referral|utmcct=/origin"
"als/chi-12-indispensable-digital-tools-bsi,0,0.storygallery">>, [
{<<"refk">>, <<"sOUZDzq2w2">>},
{<<"sk">>, <<"B602064E0139D842D620C7569640DBB4C81C45080651"
"9CC124EF794863E10E80">>},
{<<"__utma">>, <<"64249653.825741573.1380181332.1400"
"015657.1400019557.703">>},
{<<"__utmb">>, <<"64249653.1.10.1400019557">>},
{<<"__utmc">>, <<"64249653">>},
{<<"__utmz">>, <<"64249653.1400019557.703.13.utmcsr=bluesky.chic"
"agotribune.com|utmccn=(referral)|utmcmd=referral|utmcct=/origin"
"als/chi-12-indispensable-digital-tools-bsi,0,0.storygallery">>}
]},
%% Potential edge cases (initially from Mochiweb).
{<<"foo=\\x">>, [{<<"foo">>, <<"\\x">>}]},
{<<"=">>, {error, badarg}},
{<<" foo ; bar ">>, {error, badarg}},
{<<"foo=;bar=">>, [{<<"foo">>, <<>>}, {<<"bar">>, <<>>}]},
{<<"foo=\\\";;bar ">>, {error, badarg}},
{<<"foo=\\\";;bar=good ">>,
[{<<"foo">>, <<"\\\"">>}, {<<"bar">>, <<"good">>}]},
{<<"foo=\"\\\";bar">>, {error, badarg}},
{<<>>, []},
{<<"foo=bar , baz=wibble ">>, [{<<"foo">>, <<"bar , baz=wibble">>}]}
],
[{V, fun() -> R = parse_cookie(V) end} || {V, R} <- Tests].
-endif.
%% @doc Convert a cookie name, value and options to its iodata form.
%% @end
%%
%% Initially from Mochiweb:
%% * Copyright 2007 Mochi Media, Inc.
%% Initial binary implementation:
%% * Copyright 2011 Thomas Burdick <thomas.burdick@gmail.com>
-spec setcookie(iodata(), iodata(), cookie_opts()) -> iodata().
setcookie(Name, Value, Opts) ->
nomatch = binary:match(iolist_to_binary(Name), [<<$=>>, <<$,>>, <<$;>>,
<<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
nomatch = binary:match(iolist_to_binary(Value), [<<$,>>, <<$;>>,
<<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
MaxAgeBin = case lists:keyfind(max_age, 1, Opts) of
false -> <<>>;
{_, 0} ->
%% MSIE requires an Expires date in the past to delete a cookie.
<<"; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0">>;
{_, MaxAge} when is_integer(MaxAge), MaxAge > 0 ->
UTC = calendar:universal_time(),
Secs = calendar:datetime_to_gregorian_seconds(UTC),
Expires = calendar:gregorian_seconds_to_datetime(Secs + MaxAge),
[<<"; Expires=">>, cow_date:rfc2109(Expires),
<<"; Max-Age=">>, integer_to_list(MaxAge)]
end,
DomainBin = case lists:keyfind(domain, 1, Opts) of
false -> <<>>;
{_, Domain} -> [<<"; Domain=">>, Domain]
end,
PathBin = case lists:keyfind(path, 1, Opts) of
false -> <<>>;
{_, Path} -> [<<"; Path=">>, Path]
end,
SecureBin = case lists:keyfind(secure, 1, Opts) of
false -> <<>>;
{_, false} -> <<>>;
{_, true} -> <<"; Secure">>
end,
HttpOnlyBin = case lists:keyfind(http_only, 1, Opts) of
false -> <<>>;
{_, false} -> <<>>;
{_, true} -> <<"; HttpOnly">>
end,
[Name, <<"=">>, Value, <<"; Version=1">>,
MaxAgeBin, DomainBin, PathBin, SecureBin, HttpOnlyBin].
-ifdef(TEST).
setcookie_test_() ->
%% {Name, Value, Opts, Result}
Tests = [
{<<"Customer">>, <<"WILE_E_COYOTE">>,
[{http_only, true}, {domain, <<"acme.com">>}],
<<"Customer=WILE_E_COYOTE; Version=1; "
"Domain=acme.com; HttpOnly">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
[{path, <<"/acme">>}],
<<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
[{secure, true}],
<<"Customer=WILE_E_COYOTE; Version=1; Secure">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
[{secure, false}, {http_only, false}],
<<"Customer=WILE_E_COYOTE; Version=1">>},
{<<"Customer">>, <<"WILE_E_COYOTE">>,
[{path, <<"/acme">>}, {badoption, <<"negatory">>}],
<<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>}
],
[{R, fun() -> R = iolist_to_binary(setcookie(N, V, O)) end}
|| {N, V, O, R} <- Tests].
setcookie_max_age_test() ->
F = fun(N, V, O) ->
binary:split(iolist_to_binary(
setcookie(N, V, O)), <<";">>, [global])
end,
[<<"Customer=WILE_E_COYOTE">>,
<<" Version=1">>,
<<" Expires=", _/binary>>,
<<" Max-Age=111">>,
<<" Secure">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
[{max_age, 111}, {secure, true}]),
case catch F(<<"Customer">>, <<"WILE_E_COYOTE">>, [{max_age, -111}]) of
{'EXIT', {{case_clause, {max_age, -111}}, _}} -> ok
end,
[<<"Customer=WILE_E_COYOTE">>,
<<" Version=1">>,
<<" Expires=", _/binary>>,
<<" Max-Age=86417">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
[{max_age, 86417}]),
ok.
setcookie_failures_test_() ->
F = fun(N, V) ->
try setcookie(N, V, []) of
_ ->
false
catch _:_ ->
true
end
end,
Tests = [
{<<"Na=me">>, <<"Value">>},
{<<"Name;">>, <<"Value">>},
{<<"\r\name">>, <<"Value">>},
{<<"Name">>, <<"Value;">>},
{<<"Name">>, <<"\value">>}
],
[{iolist_to_binary(io_lib:format("{~p, ~p} failure", [N, V])),
fun() -> true = F(N, V) end}
|| {N, V} <- Tests].
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_date).
-export([rfc2109/1]).
%% @doc Return the date formatted according to RFC2109.
-spec rfc2109(calendar:datetime()) -> binary().
rfc2109({Date = {Y, Mo, D}, {H, Mi, S}}) ->
Wday = calendar:day_of_the_week(Date),
<< (weekday(Wday))/binary, ", ",
(pad_int(D))/binary, "-",
(month(Mo))/binary, "-",
(year(Y))/binary, " ",
(pad_int(H))/binary, ":",
(pad_int(Mi))/binary, ":",
(pad_int(S))/binary, " GMT" >>.
-ifdef(TEST).
rfc2109_test_() ->
Tests = [
{<<"Sat, 14-May-2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}},
{<<"Sun, 01-Jan-2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}}
],
[{R, fun() -> R = rfc2109(D) end} || {R, D} <- Tests].
-endif.
-ifdef(PERF).
horse_rfc2019_20130101_000000() ->
horse:repeat(100000,
rfc2109({{2013, 1, 1}, {0, 0, 0}})
).
horse_rfc2019_20131231_235959() ->
horse:repeat(100000,
rfc2109({{2013, 12, 31}, {23, 59, 59}})
).
horse_rfc2019_12340506_070809() ->
horse:repeat(100000,
rfc2109({{1234, 5, 6}, {7, 8, 9}})
).
-endif.
%% Internal.
-spec pad_int(0..59) -> <<_:16>>.
pad_int( 0) -> <<"00">>;
pad_int( 1) -> <<"01">>;
pad_int( 2) -> <<"02">>;
pad_int( 3) -> <<"03">>;
pad_int( 4) -> <<"04">>;
pad_int( 5) -> <<"05">>;
pad_int( 6) -> <<"06">>;
pad_int( 7) -> <<"07">>;
pad_int( 8) -> <<"08">>;
pad_int( 9) -> <<"09">>;
pad_int(10) -> <<"10">>;
pad_int(11) -> <<"11">>;
pad_int(12) -> <<"12">>;
pad_int(13) -> <<"13">>;
pad_int(14) -> <<"14">>;
pad_int(15) -> <<"15">>;
pad_int(16) -> <<"16">>;
pad_int(17) -> <<"17">>;
pad_int(18) -> <<"18">>;
pad_int(19) -> <<"19">>;
pad_int(20) -> <<"20">>;
pad_int(21) -> <<"21">>;
pad_int(22) -> <<"22">>;
pad_int(23) -> <<"23">>;
pad_int(24) -> <<"24">>;
pad_int(25) -> <<"25">>;
pad_int(26) -> <<"26">>;
pad_int(27) -> <<"27">>;
pad_int(28) -> <<"28">>;
pad_int(29) -> <<"29">>;
pad_int(30) -> <<"30">>;
pad_int(31) -> <<"31">>;
pad_int(32) -> <<"32">>;
pad_int(33) -> <<"33">>;
pad_int(34) -> <<"34">>;
pad_int(35) -> <<"35">>;
pad_int(36) -> <<"36">>;
pad_int(37) -> <<"37">>;
pad_int(38) -> <<"38">>;
pad_int(39) -> <<"39">>;
pad_int(40) -> <<"40">>;
pad_int(41) -> <<"41">>;
pad_int(42) -> <<"42">>;
pad_int(43) -> <<"43">>;
pad_int(44) -> <<"44">>;
pad_int(45) -> <<"45">>;
pad_int(46) -> <<"46">>;
pad_int(47) -> <<"47">>;
pad_int(48) -> <<"48">>;
pad_int(49) -> <<"49">>;
pad_int(50) -> <<"50">>;
pad_int(51) -> <<"51">>;
pad_int(52) -> <<"52">>;
pad_int(53) -> <<"53">>;
pad_int(54) -> <<"54">>;
pad_int(55) -> <<"55">>;
pad_int(56) -> <<"56">>;
pad_int(57) -> <<"57">>;
pad_int(58) -> <<"58">>;
pad_int(59) -> <<"59">>.
-spec weekday(1..7) -> <<_:24>>.
weekday(1) -> <<"Mon">>;
weekday(2) -> <<"Tue">>;
weekday(3) -> <<"Wed">>;
weekday(4) -> <<"Thu">>;
weekday(5) -> <<"Fri">>;
weekday(6) -> <<"Sat">>;
weekday(7) -> <<"Sun">>.
-spec month(1..12) -> <<_:24>>.
month( 1) -> <<"Jan">>;
month( 2) -> <<"Feb">>;
month( 3) -> <<"Mar">>;
month( 4) -> <<"Apr">>;
month( 5) -> <<"May">>;
month( 6) -> <<"Jun">>;
month( 7) -> <<"Jul">>;
month( 8) -> <<"Aug">>;
month( 9) -> <<"Sep">>;
month(10) -> <<"Oct">>;
month(11) -> <<"Nov">>;
month(12) -> <<"Dec">>.
-spec year(pos_integer()) -> <<_:32>>.
year(1970) -> <<"1970">>;
year(1971) -> <<"1971">>;
year(1972) -> <<"1972">>;
year(1973) -> <<"1973">>;
year(1974) -> <<"1974">>;
year(1975) -> <<"1975">>;
year(1976) -> <<"1976">>;
year(1977) -> <<"1977">>;
year(1978) -> <<"1978">>;
year(1979) -> <<"1979">>;
year(1980) -> <<"1980">>;
year(1981) -> <<"1981">>;
year(1982) -> <<"1982">>;
year(1983) -> <<"1983">>;
year(1984) -> <<"1984">>;
year(1985) -> <<"1985">>;
year(1986) -> <<"1986">>;
year(1987) -> <<"1987">>;
year(1988) -> <<"1988">>;
year(1989) -> <<"1989">>;
year(1990) -> <<"1990">>;
year(1991) -> <<"1991">>;
year(1992) -> <<"1992">>;
year(1993) -> <<"1993">>;
year(1994) -> <<"1994">>;
year(1995) -> <<"1995">>;
year(1996) -> <<"1996">>;
year(1997) -> <<"1997">>;
year(1998) -> <<"1998">>;
year(1999) -> <<"1999">>;
year(2000) -> <<"2000">>;
year(2001) -> <<"2001">>;
year(2002) -> <<"2002">>;
year(2003) -> <<"2003">>;
year(2004) -> <<"2004">>;
year(2005) -> <<"2005">>;
year(2006) -> <<"2006">>;
year(2007) -> <<"2007">>;
year(2008) -> <<"2008">>;
year(2009) -> <<"2009">>;
year(2010) -> <<"2010">>;
year(2011) -> <<"2011">>;
year(2012) -> <<"2012">>;
year(2013) -> <<"2013">>;
year(2014) -> <<"2014">>;
year(2015) -> <<"2015">>;
year(2016) -> <<"2016">>;
year(2017) -> <<"2017">>;
year(2018) -> <<"2018">>;
year(2019) -> <<"2019">>;
year(2020) -> <<"2020">>;
year(2021) -> <<"2021">>;
year(2022) -> <<"2022">>;
year(2023) -> <<"2023">>;
year(2024) -> <<"2024">>;
year(2025) -> <<"2025">>;
year(2026) -> <<"2026">>;
year(2027) -> <<"2027">>;
year(2028) -> <<"2028">>;
year(2029) -> <<"2029">>;
year(Year) -> list_to_binary(integer_to_list(Year)).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_http).
%% @todo parse_request_line
-export([parse_status_line/1]).
-export([parse_headers/1]).
-export([parse_fullhost/1]).
-export([parse_fullpath/1]).
-export([parse_version/1]).
-export([request/4]).
-export([version/1]).
-type version() :: 'HTTP/1.0' | 'HTTP/1.1'.
-type status() :: 100..999.
-type headers() :: [{binary(), iodata()}].
-include("cow_inline.hrl").
%% @doc Parse the status line.
-spec parse_status_line(binary()) -> {version(), status(), binary(), binary()}.
parse_status_line(<< "HTTP/1.1 200 OK\r\n", Rest/bits >>) ->
{'HTTP/1.1', 200, <<"OK">>, Rest};
parse_status_line(<< "HTTP/1.1 404 Not Found\r\n", Rest/bits >>) ->
{'HTTP/1.1', 404, <<"Not Found">>, Rest};
parse_status_line(<< "HTTP/1.1 500 Internal Server Error\r\n", Rest/bits >>) ->
{'HTTP/1.1', 500, <<"Internal Server Error">>, Rest};
parse_status_line(<< "HTTP/1.1 ", Status/bits >>) ->
parse_status_line(Status, 'HTTP/1.1');
parse_status_line(<< "HTTP/1.0 ", Status/bits >>) ->
parse_status_line(Status, 'HTTP/1.0').
parse_status_line(<< H, T, U, " ", Rest/bits >>, Version)
when $0 =< H, H =< $9, $0 =< T, T =< $9, $0 =< U, U =< $9 ->
Status = (H - $0) * 100 + (T - $0) * 10 + (U - $0),
{Pos, _} = binary:match(Rest, <<"\r">>),
<< StatusStr:Pos/binary, "\r\n", Rest2/bits >> = Rest,
{Version, Status, StatusStr, Rest2}.
-ifdef(TEST).
parse_status_line_test_() ->
Tests = [
{<<"HTTP/1.1 200 OK\r\nRest">>,
{'HTTP/1.1', 200, <<"OK">>, <<"Rest">>}},
{<<"HTTP/1.0 404 Not Found\r\nRest">>,
{'HTTP/1.0', 404, <<"Not Found">>, <<"Rest">>}},
{<<"HTTP/1.1 500 Something very funny here\r\nRest">>,
{'HTTP/1.1', 500, <<"Something very funny here">>, <<"Rest">>}},
{<<"HTTP/1.1 200 \r\nRest">>,
{'HTTP/1.1', 200, <<>>, <<"Rest">>}}
],
[{V, fun() -> R = parse_status_line(V) end}
|| {V, R} <- Tests].
parse_status_line_error_test_() ->
Tests = [
<<>>,
<<"HTTP/1.1">>,
<<"HTTP/1.1 200\r\n">>,
<<"HTTP/1.1 200 OK">>,
<<"HTTP/1.1 200 OK\r">>,
<<"HTTP/1.1 200 OK\n">>,
<<"HTTP/0.9 200 OK\r\n">>,
<<"HTTP/1.1 42 Answer\r\n">>,
<<"HTTP/1.1 999999999 More than OK\r\n">>,
<<"content-type: text/plain\r\n">>,
<<0:80, "\r\n">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_status_line(V)) end}
|| V <- Tests].
-endif.
-ifdef(PERF).
horse_parse_status_line_200() ->
horse:repeat(200000,
parse_status_line(<<"HTTP/1.1 200 OK\r\n">>)
).
horse_parse_status_line_404() ->
horse:repeat(200000,
parse_status_line(<<"HTTP/1.1 404 Not Found\r\n">>)
).
horse_parse_status_line_500() ->
horse:repeat(200000,
parse_status_line(<<"HTTP/1.1 500 Internal Server Error\r\n">>)
).
horse_parse_status_line_other() ->
horse:repeat(200000,
parse_status_line(<<"HTTP/1.1 416 Requested range not satisfiable\r\n">>)
).
-endif.
%% @doc Parse the list of headers.
-spec parse_headers(binary()) -> {[{binary(), binary()}], binary()}.
parse_headers(Data) ->
parse_header(Data, []).
parse_header(<< $\r, $\n, Rest/bits >>, Acc) ->
{lists:reverse(Acc), Rest};
parse_header(Data, Acc) ->
parse_hd_name(Data, Acc, <<>>).
parse_hd_name(<< C, Rest/bits >>, Acc, SoFar) ->
case C of
$: -> parse_hd_before_value(Rest, Acc, SoFar);
$\s -> parse_hd_name_ws(Rest, Acc, SoFar);
$\t -> parse_hd_name_ws(Rest, Acc, SoFar);
?INLINE_LOWERCASE(parse_hd_name, Rest, Acc, SoFar)
end.
parse_hd_name_ws(<< C, Rest/bits >>, Acc, Name) ->
case C of
$: -> parse_hd_before_value(Rest, Acc, Name);
$\s -> parse_hd_name_ws(Rest, Acc, Name);
$\t -> parse_hd_name_ws(Rest, Acc, Name)
end.
parse_hd_before_value(<< $\s, Rest/bits >>, Acc, Name) ->
parse_hd_before_value(Rest, Acc, Name);
parse_hd_before_value(<< $\t, Rest/bits >>, Acc, Name) ->
parse_hd_before_value(Rest, Acc, Name);
parse_hd_before_value(Data, Acc, Name) ->
parse_hd_value(Data, Acc, Name, <<>>).
parse_hd_value(<< $\r, Rest/bits >>, Acc, Name, SoFar) ->
case Rest of
<< $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
parse_hd_value(Rest2, Acc, Name, << SoFar/binary, C >>);
<< $\n, Rest2/bits >> ->
parse_header(Rest2, [{Name, SoFar}|Acc])
end;
parse_hd_value(<< C, Rest/bits >>, Acc, Name, SoFar) ->
parse_hd_value(Rest, Acc, Name, << SoFar/binary, C >>).
-ifdef(TEST).
parse_headers_test_() ->
Tests = [
{<<"\r\nRest">>,
{[], <<"Rest">>}},
{<<"Server: Erlang/R17\r\n"
"Date: Sun, 23 Feb 2014 09:30:39 GMT\r\n"
"Multiline-Header: why hello!\r\n"
" I didn't see you all the way over there!\r\n"
"Content-Length: 12\r\n"
"Content-Type: text/plain\r\n"
"\r\nRest">>,
{[{<<"server">>, <<"Erlang/R17">>},
{<<"date">>, <<"Sun, 23 Feb 2014 09:30:39 GMT">>},
{<<"multiline-header">>,
<<"why hello! I didn't see you all the way over there!">>},
{<<"content-length">>, <<"12">>},
{<<"content-type">>, <<"text/plain">>}],
<<"Rest">>}}
],
[{V, fun() -> R = parse_headers(V) end}
|| {V, R} <- Tests].
parse_headers_error_test_() ->
Tests = [
<<>>,
<<"\r">>,
<<"Malformed\r\n\r\n">>,
<<"content-type: text/plain\r\nMalformed\r\n\r\n">>,
<<"HTTP/1.1 200 OK\r\n\r\n">>,
<<0:80, "\r\n\r\n">>,
<<"content-type: text/plain\r\ncontent-length: 12\r\n">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_headers(V)) end}
|| V <- Tests].
-endif.
-ifdef(PERF).
horse_parse_headers() ->
horse:repeat(50000,
parse_headers(<<"Server: Erlang/R17\r\n"
"Date: Sun, 23 Feb 2014 09:30:39 GMT\r\n"
"Multiline-Header: why hello!\r\n"
" I didn't see you all the way over there!\r\n"
"Content-Length: 12\r\n"
"Content-Type: text/plain\r\n"
"\r\nRest">>)
).
-endif.
%% @doc Extract host and port from a binary.
%%
%% Because the hostname is case insensitive it is converted
%% to lowercase.
-spec parse_fullhost(binary()) -> {binary(), undefined | non_neg_integer()}.
parse_fullhost(Fullhost) ->
parse_fullhost(Fullhost, false, <<>>).
parse_fullhost(<< $[, Rest/bits >>, false, <<>>) ->
parse_fullhost(Rest, true, << $[ >>);
parse_fullhost(<<>>, false, Acc) ->
{Acc, undefined};
%% @todo Optimize.
parse_fullhost(<< $:, Rest/bits >>, false, Acc) ->
{Acc, list_to_integer(binary_to_list(Rest))};
parse_fullhost(<< $], Rest/bits >>, true, Acc) ->
parse_fullhost(Rest, false, << Acc/binary, $] >>);
parse_fullhost(<< C, Rest/bits >>, E, Acc) ->
case C of
?INLINE_LOWERCASE(parse_fullhost, Rest, E, Acc)
end.
-ifdef(TEST).
parse_fullhost_test() ->
{<<"example.org">>, 8080} = parse_fullhost(<<"example.org:8080">>),
{<<"example.org">>, undefined} = parse_fullhost(<<"example.org">>),
{<<"192.0.2.1">>, 8080} = parse_fullhost(<<"192.0.2.1:8080">>),
{<<"192.0.2.1">>, undefined} = parse_fullhost(<<"192.0.2.1">>),
{<<"[2001:db8::1]">>, 8080} = parse_fullhost(<<"[2001:db8::1]:8080">>),
{<<"[2001:db8::1]">>, undefined} = parse_fullhost(<<"[2001:db8::1]">>),
{<<"[::ffff:192.0.2.1]">>, 8080}
= parse_fullhost(<<"[::ffff:192.0.2.1]:8080">>),
{<<"[::ffff:192.0.2.1]">>, undefined}
= parse_fullhost(<<"[::ffff:192.0.2.1]">>),
ok.
-endif.
%% @doc Extract path and query string from a binary.
-spec parse_fullpath(binary()) -> {binary(), binary()}.
parse_fullpath(Fullpath) ->
parse_fullpath(Fullpath, <<>>).
parse_fullpath(<<>>, Path) ->
{Path, <<>>};
parse_fullpath(<< $?, Qs/binary >>, Path) ->
{Path, Qs};
parse_fullpath(<< C, Rest/binary >>, SoFar) ->
parse_fullpath(Rest, << SoFar/binary, C >>).
-ifdef(TEST).
parse_fullpath_test() ->
{<<"*">>, <<>>} = parse_fullpath(<<"*">>),
{<<"/">>, <<>>} = parse_fullpath(<<"/">>),
{<<"/path/to/resource">>, <<>>} = parse_fullpath(<<"/path/to/resource">>),
{<<"/">>, <<>>} = parse_fullpath(<<"/?">>),
{<<"/">>, <<"q=cowboy">>} = parse_fullpath(<<"/?q=cowboy">>),
{<<"/path/to/resource">>, <<"q=cowboy">>}
= parse_fullpath(<<"/path/to/resource?q=cowboy">>),
ok.
-endif.
%% @doc Convert an HTTP version to atom.
-spec parse_version(binary()) -> version().
parse_version(<<"HTTP/1.1">>) -> 'HTTP/1.1';
parse_version(<<"HTTP/1.0">>) -> 'HTTP/1.0'.
-ifdef(TEST).
parse_version_test() ->
'HTTP/1.1' = parse_version(<<"HTTP/1.1">>),
'HTTP/1.0' = parse_version(<<"HTTP/1.0">>),
{'EXIT', _} = (catch parse_version(<<"HTTP/1.2">>)),
ok.
-endif.
%% @doc Return formatted request-line and headers.
%% @todo Add tests when the corresponding reverse functions are added.
-spec request(binary(), iodata(), version(), headers()) -> iodata().
request(Method, Path, Version, Headers) ->
[Method, <<" ">>, Path, <<" ">>, version(Version), <<"\r\n">>,
[[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers],
<<"\r\n">>].
%% @doc Return the version as a binary.
-spec version(version()) -> binary().
version('HTTP/1.1') -> <<"HTTP/1.1">>;
version('HTTP/1.0') -> <<"HTTP/1.0">>.
-ifdef(TEST).
version_test() ->
<<"HTTP/1.1">> = version('HTTP/1.1'),
<<"HTTP/1.0">> = version('HTTP/1.0'),
{'EXIT', _} = (catch version('HTTP/1.2')),
ok.
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
%% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_http_hd).
-export([parse_connection/1]).
-export([parse_content_length/1]).
-export([parse_transfer_encoding/1]).
-include("cow_inline.hrl").
%% @doc Parse the Connection header.
-spec parse_connection(binary()) -> [binary()].
parse_connection(<<"close">>) ->
[<<"close">>];
parse_connection(<<"keep-alive">>) ->
[<<"keep-alive">>];
parse_connection(Connection) ->
nonempty(token_ci_list(Connection, [])).
-ifdef(TEST).
parse_connection_test_() ->
Tests = [
{<<"close">>, [<<"close">>]},
{<<"ClOsE">>, [<<"close">>]},
{<<"Keep-Alive">>, [<<"keep-alive">>]},
{<<"keep-alive, Upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
],
[{V, fun() -> R = parse_connection(V) end} || {V, R} <- Tests].
-endif.
-ifdef(PERF).
horse_parse_connection_close() ->
horse:repeat(200000,
parse_connection(<<"close">>)
).
horse_parse_connection_keepalive() ->
horse:repeat(200000,
parse_connection(<<"keep-alive">>)
).
horse_parse_connection_keepalive_upgrade() ->
horse:repeat(200000,
parse_connection(<<"keep-alive, upgrade">>)
).
-endif.
%% @doc Parse the Content-Length header.
%%
%% The value has at least one digit, and may be followed by whitespace.
-spec parse_content_length(binary()) -> non_neg_integer().
parse_content_length(<< $0 >>) -> 0;
parse_content_length(<< $0, R/bits >>) -> number(R, 0);
parse_content_length(<< $1, R/bits >>) -> number(R, 1);
parse_content_length(<< $2, R/bits >>) -> number(R, 2);
parse_content_length(<< $3, R/bits >>) -> number(R, 3);
parse_content_length(<< $4, R/bits >>) -> number(R, 4);
parse_content_length(<< $5, R/bits >>) -> number(R, 5);
parse_content_length(<< $6, R/bits >>) -> number(R, 6);
parse_content_length(<< $7, R/bits >>) -> number(R, 7);
parse_content_length(<< $8, R/bits >>) -> number(R, 8);
parse_content_length(<< $9, R/bits >>) -> number(R, 9).
-ifdef(TEST).
parse_content_length_test_() ->
Tests = [
{<<"0">>, 0},
{<<"42 ">>, 42},
{<<"69\t">>, 69},
{<<"1337">>, 1337},
{<<"1234567890">>, 1234567890},
{<<"1234567890 ">>, 1234567890}
],
[{V, fun() -> R = parse_content_length(V) end} || {V, R} <- Tests].
-endif.
-ifdef(PERF).
horse_parse_content_length_zero() ->
horse:repeat(100000,
parse_content_length(<<"0">>)
).
horse_parse_content_length_giga() ->
horse:repeat(100000,
parse_content_length(<<"1234567890">>)
).
-endif.
%% @doc Parse the Transfer-Encoding header.
%%
%% @todo Extension parameters.
-spec parse_transfer_encoding(binary()) -> [binary()].
parse_transfer_encoding(<<"chunked">>) ->
[<<"chunked">>];
parse_transfer_encoding(TransferEncoding) ->
nonempty(token_ci_list(TransferEncoding, [])).
-ifdef(TEST).
parse_transfer_encoding_test_() ->
Tests = [
{<<"a , , , ">>, [<<"a">>]},
{<<" , , , a">>, [<<"a">>]},
{<<"a , , b">>, [<<"a">>, <<"b">>]},
{<<"chunked">>, [<<"chunked">>]},
{<<"chunked, something">>, [<<"chunked">>, <<"something">>]}
],
[{V, fun() -> R = parse_transfer_encoding(V) end} || {V, R} <- Tests].
parse_transfer_encoding_error_test_() ->
Tests = [
<<>>,
<<" ">>,
<<" , ">>,
<<",,,">>,
<<"a b">>
],
[{V, fun() -> {'EXIT', _} = (catch parse_transfer_encoding(V)) end}
|| V <- Tests].
-endif.
-ifdef(PERF).
horse_parse_transfer_encoding_chunked() ->
horse:repeat(200000,
parse_transfer_encoding(<<"chunked">>)
).
horse_parse_transfer_encoding_custom() ->
horse:repeat(200000,
parse_transfer_encoding(<<"chunked, something">>)
).
-endif.
%% Internal.
%% Only return if the list is not empty.
nonempty(L) when L =/= [] -> L.
%% Parse a number optionally followed by whitespace.
number(<< $0, R/bits >>, Acc) -> number(R, Acc * 10);
number(<< $1, R/bits >>, Acc) -> number(R, Acc * 10 + 1);
number(<< $2, R/bits >>, Acc) -> number(R, Acc * 10 + 2);
number(<< $3, R/bits >>, Acc) -> number(R, Acc * 10 + 3);
number(<< $4, R/bits >>, Acc) -> number(R, Acc * 10 + 4);
number(<< $5, R/bits >>, Acc) -> number(R, Acc * 10 + 5);
number(<< $6, R/bits >>, Acc) -> number(R, Acc * 10 + 6);
number(<< $7, R/bits >>, Acc) -> number(R, Acc * 10 + 7);
number(<< $8, R/bits >>, Acc) -> number(R, Acc * 10 + 8);
number(<< $9, R/bits >>, Acc) -> number(R, Acc * 10 + 9);
number(<< $\s, R/bits >>, Acc) -> ws_end(R), Acc;
number(<< $\t, R/bits >>, Acc) -> ws_end(R), Acc;
number(<<>>, Acc) -> Acc.
ws_end(<< $\s, R/bits >>) -> ws_end(R);
ws_end(<< $\t, R/bits >>) -> ws_end(R);
ws_end(<<>>) -> ok.
%% Parse a list of case insensitive tokens.
token_ci_list(<<>>, Acc) -> lists:reverse(Acc);
token_ci_list(<< $\s, R/bits >>, Acc) -> token_ci_list(R, Acc);
token_ci_list(<< $\t, R/bits >>, Acc) -> token_ci_list(R, Acc);
token_ci_list(<< $,, R/bits >>, Acc) -> token_ci_list(R, Acc);
token_ci_list(<< C, R/bits >>, Acc) ->
case C of
?INLINE_LOWERCASE(token_ci_list, R, Acc, <<>>)
end.
token_ci_list(<<>>, Acc, T) -> lists:reverse([T|Acc]);
token_ci_list(<< $\s, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
token_ci_list(<< $\t, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
token_ci_list(<< $,, R/bits >>, Acc, T) -> token_ci_list(R, [T|Acc]);
token_ci_list(<< C, R/bits >>, Acc, T) ->
case C of
?INLINE_LOWERCASE(token_ci_list, R, Acc, T)
end.
token_ci_list_sep(<<>>, Acc, T) -> lists:reverse([T|Acc]);
token_ci_list_sep(<< $\s, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
token_ci_list_sep(<< $\t, R/bits >>, Acc, T) -> token_ci_list_sep(R, Acc, T);
token_ci_list_sep(<< $,, R/bits >>, Acc, T) -> token_ci_list(R, [T|Acc]).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
%% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_http_te).
%% Identity.
-export([stream_identity/2]).
-export([identity/1]).
%% Chunked.
-export([stream_chunked/2]).
-export([chunk/1]).
-export([last_chunk/0]).
%% The state type is the same for both identity and chunked.
-type state() :: {non_neg_integer(), non_neg_integer()}.
-type decode_ret() :: more
| {more, Data::binary(), state()}
| {more, Data::binary(), RemLen::non_neg_integer(), state()}
| {more, Data::binary(), Rest::binary(), state()}
| {done, TotalLen::non_neg_integer(), Rest::binary()}
| {done, Data::binary(), TotalLen::non_neg_integer(), Rest::binary()}.
-export_type([decode_ret/0]).
-ifdef(EXTRA).
dripfeed(<< C, Rest/bits >>, Acc, State, F) ->
case F(<< Acc/binary, C >>, State) of
more ->
dripfeed(Rest, << Acc/binary, C >>, State, F);
{more, _, State2} ->
dripfeed(Rest, <<>>, State2, F);
{more, _, Length, State2} when is_integer(Length) ->
dripfeed(Rest, <<>>, State2, F);
{more, _, Acc2, State2} ->
dripfeed(Rest, Acc2, State2, F);
{done, _, <<>>} ->
ok;
{done, _, _, <<>>} ->
ok
end.
-endif.
%% Identity.
%% @doc Decode an identity stream.
-spec stream_identity(Data, State)
-> {more, Data, Len, State} | {done, Data, Len, Data}
when Data::binary(), State::state(), Len::non_neg_integer().
stream_identity(Data, {Streamed, Total}) ->
Streamed2 = Streamed + byte_size(Data),
if
Streamed2 < Total ->
{more, Data, Total - Streamed2, {Streamed2, Total}};
true ->
Size = Total - Streamed,
<< Data2:Size/binary, Rest/bits >> = Data,
{done, Data2, Total, Rest}
end.
-spec identity(Data) -> Data when Data::iodata().
identity(Data) ->
Data.
-ifdef(TEST).
stream_identity_test() ->
{done, <<>>, 0, <<>>}
= stream_identity(identity(<<>>), {0, 0}),
{done, <<"\r\n">>, 2, <<>>}
= stream_identity(identity(<<"\r\n">>), {0, 2}),
{done, << 0:80000 >>, 10000, <<>>}
= stream_identity(identity(<< 0:80000 >>), {0, 10000}),
ok.
stream_identity_parts_test() ->
{more, << 0:8000 >>, 1999, S1}
= stream_identity(<< 0:8000 >>, {0, 2999}),
{more, << 0:8000 >>, 999, S2}
= stream_identity(<< 0:8000 >>, S1),
{done, << 0:7992 >>, 2999, <<>>}
= stream_identity(<< 0:7992 >>, S2),
ok.
-endif.
-ifdef(PERF).
%% Using the same data as the chunked one for comparison.
horse_stream_identity() ->
horse:repeat(10000,
stream_identity(<<
"4\r\n"
"Wiki\r\n"
"5\r\n"
"pedia\r\n"
"e\r\n"
" in\r\n\r\nchunks.\r\n"
"0\r\n"
"\r\n">>, {0, 43})
).
horse_stream_identity_dripfeed() ->
horse:repeat(10000,
dripfeed(<<
"4\r\n"
"Wiki\r\n"
"5\r\n"
"pedia\r\n"
"e\r\n"
" in\r\n\r\nchunks.\r\n"
"0\r\n"
"\r\n">>, <<>>, {0, 43}, fun stream_identity/2)
).
-endif.
%% Chunked.
%% @doc Decode a chunked stream.
-spec stream_chunked(Data, State)
-> more | {more, Data, State} | {more, Data, Len, State}
| {more, Data, Data, State}
| {done, Len, Data} | {done, Data, Len, Data}
when Data::binary(), State::state(), Len::non_neg_integer().
stream_chunked(Data, State) ->
stream_chunked(Data, State, <<>>).
%% New chunk.
stream_chunked(Data = << C, _/bits >>, {0, Streamed}, Acc) when C =/= $\r ->
case chunked_len(Data, Streamed, Acc, 0) of
{next, Rest, State, Acc2} ->
stream_chunked(Rest, State, Acc2);
{more, State, Acc2} ->
{more, Acc2, Data, State};
Ret ->
Ret
end;
%% Trailing \r\n before next chunk.
stream_chunked(<< "\r\n", Rest/bits >>, {2, Streamed}, Acc) ->
stream_chunked(Rest, {0, Streamed}, Acc);
%% Trailing \r before next chunk.
stream_chunked(<< "\r" >>, {2, Streamed}, Acc) ->
{more, Acc, {1, Streamed}};
%% Trailing \n before next chunk.
stream_chunked(<< "\n", Rest/bits >>, {1, Streamed}, Acc) ->
stream_chunked(Rest, {0, Streamed}, Acc);
%% More data needed.
stream_chunked(<<>>, State = {Rem, _}, Acc) ->
{more, Acc, Rem, State};
%% Chunk data.
stream_chunked(Data, {Rem, Streamed}, Acc) when Rem > 2 ->
DataSize = byte_size(Data),
RemSize = Rem - 2,
case Data of
<< Chunk:RemSize/binary, "\r\n", Rest/bits >> ->
stream_chunked(Rest, {0, Streamed + RemSize}, << Acc/binary, Chunk/binary >>);
<< Chunk:RemSize/binary, "\r" >> ->
{more, << Acc/binary, Chunk/binary >>, {1, Streamed + RemSize}};
%% Everything in Data is part of the chunk.
_ ->
Rem2 = Rem - DataSize,
{more, << Acc/binary, Data/binary >>, Rem2, {Rem2, Streamed + DataSize}}
end.
chunked_len(<< $0, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16);
chunked_len(<< $1, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 1);
chunked_len(<< $2, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 2);
chunked_len(<< $3, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 3);
chunked_len(<< $4, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 4);
chunked_len(<< $5, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 5);
chunked_len(<< $6, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 6);
chunked_len(<< $7, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 7);
chunked_len(<< $8, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 8);
chunked_len(<< $9, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 9);
chunked_len(<< $A, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
chunked_len(<< $B, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
chunked_len(<< $C, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
chunked_len(<< $D, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
chunked_len(<< $E, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
chunked_len(<< $F, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
chunked_len(<< $a, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
chunked_len(<< $b, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
chunked_len(<< $c, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
chunked_len(<< $d, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
chunked_len(<< $e, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
chunked_len(<< $f, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
%% Final chunk.
chunked_len(<< "\r\n\r\n", R/bits >>, S, <<>>, 0) -> {done, S, R};
chunked_len(<< "\r\n\r\n", R/bits >>, S, A, 0) -> {done, A, S, R};
chunked_len(_, _, _, 0) -> more;
%% Normal chunk. Add 2 to Len for the trailing \r\n.
chunked_len(<< "\r\n", R/bits >>, S, A, Len) -> {next, R, {Len + 2, S}, A};
chunked_len(<<"\r">>, _, <<>>, _) -> more;
chunked_len(<<"\r">>, S, A, _) -> {more, {0, S}, A};
chunked_len(<<>>, _, <<>>, _) -> more;
chunked_len(<<>>, S, A, _) -> {more, {0, S}, A}.
%% @doc Encode a chunk.
-spec chunk(D) -> D when D::iodata().
chunk(Data) ->
[integer_to_list(iolist_size(Data), 16), <<"\r\n">>,
Data, <<"\r\n">>].
%% @doc Encode the last chunk of a chunked stream.
-spec last_chunk() -> << _:40 >>.
last_chunk() ->
<<"0\r\n\r\n">>.
-ifdef(TEST).
stream_chunked_identity_test() ->
{done, <<"Wikipedia in\r\n\r\nchunks.">>, 23, <<>>}
= stream_chunked(iolist_to_binary([
chunk("Wiki"),
chunk("pedia"),
chunk(" in\r\n\r\nchunks."),
last_chunk()
]), {0, 0}),
ok.
stream_chunked_one_pass_test() ->
{done, 0, <<>>} = stream_chunked(<<"0\r\n\r\n">>, {0, 0}),
{done, <<"Wikipedia in\r\n\r\nchunks.">>, 23, <<>>}
= stream_chunked(<<
"4\r\n"
"Wiki\r\n"
"5\r\n"
"pedia\r\n"
"e\r\n"
" in\r\n\r\nchunks.\r\n"
"0\r\n"
"\r\n">>, {0, 0}),
ok.
stream_chunked_n_passes_test() ->
S0 = {0, 0},
more = stream_chunked(<<"4\r">>, S0),
{more, <<>>, 6, S1} = stream_chunked(<<"4\r\n">>, S0),
{more, <<"Wiki">>, 0, S2} = stream_chunked(<<"Wiki\r\n">>, S1),
{more, <<"pedia">>, <<"e\r">>, S3} = stream_chunked(<<"5\r\npedia\r\ne\r">>, S2),
{more, <<" in\r\n\r\nchunks.">>, 2, S4} = stream_chunked(<<"e\r\n in\r\n\r\nchunks.">>, S3),
{done, 23, <<>>} = stream_chunked(<<"\r\n0\r\n\r\n">>, S4),
%% A few extra for coverage purposes.
more = stream_chunked(<<"\n3">>, {1, 0}),
{more, <<"abc">>, 2, {2, 3}} = stream_chunked(<<"\n3\r\nabc">>, {1, 0}),
{more, <<"abc">>, {1, 3}} = stream_chunked(<<"3\r\nabc\r">>, {0, 0}),
{more, <<"abc">>, <<"123">>, {0, 3}} = stream_chunked(<<"3\r\nabc\r\n123">>, {0, 0}),
ok.
stream_chunked_dripfeed_test() ->
dripfeed(<<
"4\r\n"
"Wiki\r\n"
"5\r\n"
"pedia\r\n"
"e\r\n"
" in\r\n\r\nchunks.\r\n"
"0\r\n"
"\r\n">>, <<>>, {0, 0}, fun stream_chunked/2).
do_body_to_chunks(_, <<>>, Acc) ->
lists:reverse([<<"0\r\n\r\n">>|Acc]);
do_body_to_chunks(ChunkSize, Body, Acc) ->
BodySize = byte_size(Body),
ChunkSize2 = case BodySize < ChunkSize of
true -> BodySize;
false -> ChunkSize
end,
<< Chunk:ChunkSize2/binary, Rest/binary >> = Body,
ChunkSizeBin = list_to_binary(integer_to_list(ChunkSize2, 16)),
do_body_to_chunks(ChunkSize, Rest,
[<< ChunkSizeBin/binary, "\r\n", Chunk/binary, "\r\n" >>|Acc]).
stream_chunked_dripfeed2_test() ->
Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
Body2 = iolist_to_binary(do_body_to_chunks(50, Body, [])),
dripfeed(Body2, <<>>, {0, 0}, fun stream_chunked/2).
stream_chunked_error_test_() ->
Tests = [
{<<>>, undefined},
{<<"\n\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, {2, 0}}
],
[{lists:flatten(io_lib:format("value ~p state ~p", [V, S])),
fun() -> {'EXIT', _} = (catch stream_chunked(V, S)) end}
|| {V, S} <- Tests].
-endif.
-ifdef(PERF).
horse_stream_chunked() ->
horse:repeat(10000,
stream_chunked(<<
"4\r\n"
"Wiki\r\n"
"5\r\n"
"pedia\r\n"
"e\r\n"
" in\r\n\r\nchunks.\r\n"
"0\r\n"
"\r\n">>, {0, 0})
).
horse_stream_chunked_dripfeed() ->
horse:repeat(10000,
dripfeed(<<
"4\r\n"
"Wiki\r\n"
"5\r\n"
"pedia\r\n"
"e\r\n"
" in\r\n\r\nchunks.\r\n"
"0\r\n"
"\r\n">>, <<>>, {0, 43}, fun stream_chunked/2)
).
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_mimetypes).
-export([all/1]).
-export([web/1]).
%% @doc Return the mimetype for any file by looking at its extension.
-spec all(binary()) -> {binary(), binary(), []}.
all(Path) ->
case filename:extension(Path) of
<<>> -> {<<"application">>, <<"octet-stream">>, []};
<< $., Ext/binary >> -> all_ext(Ext)
end.
%% @doc Return the mimetype for a Web related file by looking at its extension.
-spec web(binary()) -> {binary(), binary(), []}.
web(Path) ->
case filename:extension(Path) of
<<>> -> {<<"application">>, <<"octet-stream">>, []};
<< $., Ext/binary >> -> web_ext(Ext)
end.
%% Internal.
%% GENERATED
all_ext(<<"123">>) -> {<<"application">>, <<"vnd.lotus-1-2-3">>, []};
all_ext(<<"3dml">>) -> {<<"text">>, <<"vnd.in3d.3dml">>, []};
all_ext(<<"3ds">>) -> {<<"image">>, <<"x-3ds">>, []};
all_ext(<<"3g2">>) -> {<<"video">>, <<"3gpp2">>, []};
all_ext(<<"3gp">>) -> {<<"video">>, <<"3gpp">>, []};
all_ext(<<"7z">>) -> {<<"application">>, <<"x-7z-compressed">>, []};
all_ext(<<"aab">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
all_ext(<<"aac">>) -> {<<"audio">>, <<"x-aac">>, []};
all_ext(<<"aam">>) -> {<<"application">>, <<"x-authorware-map">>, []};
all_ext(<<"aas">>) -> {<<"application">>, <<"x-authorware-seg">>, []};
all_ext(<<"abw">>) -> {<<"application">>, <<"x-abiword">>, []};
all_ext(<<"ac">>) -> {<<"application">>, <<"pkix-attr-cert">>, []};
all_ext(<<"acc">>) -> {<<"application">>, <<"vnd.americandynamics.acc">>, []};
all_ext(<<"ace">>) -> {<<"application">>, <<"x-ace-compressed">>, []};
all_ext(<<"acu">>) -> {<<"application">>, <<"vnd.acucobol">>, []};
all_ext(<<"acutc">>) -> {<<"application">>, <<"vnd.acucorp">>, []};
all_ext(<<"adp">>) -> {<<"audio">>, <<"adpcm">>, []};
all_ext(<<"aep">>) -> {<<"application">>, <<"vnd.audiograph">>, []};
all_ext(<<"afm">>) -> {<<"application">>, <<"x-font-type1">>, []};
all_ext(<<"afp">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
all_ext(<<"ahead">>) -> {<<"application">>, <<"vnd.ahead.space">>, []};
all_ext(<<"ai">>) -> {<<"application">>, <<"postscript">>, []};
all_ext(<<"aif">>) -> {<<"audio">>, <<"x-aiff">>, []};
all_ext(<<"aifc">>) -> {<<"audio">>, <<"x-aiff">>, []};
all_ext(<<"aiff">>) -> {<<"audio">>, <<"x-aiff">>, []};
all_ext(<<"air">>) -> {<<"application">>, <<"vnd.adobe.air-application-installer-package+zip">>, []};
all_ext(<<"ait">>) -> {<<"application">>, <<"vnd.dvb.ait">>, []};
all_ext(<<"ami">>) -> {<<"application">>, <<"vnd.amiga.ami">>, []};
all_ext(<<"apk">>) -> {<<"application">>, <<"vnd.android.package-archive">>, []};
all_ext(<<"appcache">>) -> {<<"text">>, <<"cache-manifest">>, []};
all_ext(<<"application">>) -> {<<"application">>, <<"x-ms-application">>, []};
all_ext(<<"apr">>) -> {<<"application">>, <<"vnd.lotus-approach">>, []};
all_ext(<<"arc">>) -> {<<"application">>, <<"x-freearc">>, []};
all_ext(<<"asc">>) -> {<<"application">>, <<"pgp-signature">>, []};
all_ext(<<"asf">>) -> {<<"video">>, <<"x-ms-asf">>, []};
all_ext(<<"asm">>) -> {<<"text">>, <<"x-asm">>, []};
all_ext(<<"aso">>) -> {<<"application">>, <<"vnd.accpac.simply.aso">>, []};
all_ext(<<"asx">>) -> {<<"video">>, <<"x-ms-asf">>, []};
all_ext(<<"atc">>) -> {<<"application">>, <<"vnd.acucorp">>, []};
all_ext(<<"atom">>) -> {<<"application">>, <<"atom+xml">>, []};
all_ext(<<"atomcat">>) -> {<<"application">>, <<"atomcat+xml">>, []};
all_ext(<<"atomsvc">>) -> {<<"application">>, <<"atomsvc+xml">>, []};
all_ext(<<"atx">>) -> {<<"application">>, <<"vnd.antix.game-component">>, []};
all_ext(<<"au">>) -> {<<"audio">>, <<"basic">>, []};
all_ext(<<"avi">>) -> {<<"video">>, <<"x-msvideo">>, []};
all_ext(<<"aw">>) -> {<<"application">>, <<"applixware">>, []};
all_ext(<<"azf">>) -> {<<"application">>, <<"vnd.airzip.filesecure.azf">>, []};
all_ext(<<"azs">>) -> {<<"application">>, <<"vnd.airzip.filesecure.azs">>, []};
all_ext(<<"azw">>) -> {<<"application">>, <<"vnd.amazon.ebook">>, []};
all_ext(<<"bat">>) -> {<<"application">>, <<"x-msdownload">>, []};
all_ext(<<"bcpio">>) -> {<<"application">>, <<"x-bcpio">>, []};
all_ext(<<"bdf">>) -> {<<"application">>, <<"x-font-bdf">>, []};
all_ext(<<"bdm">>) -> {<<"application">>, <<"vnd.syncml.dm+wbxml">>, []};
all_ext(<<"bed">>) -> {<<"application">>, <<"vnd.realvnc.bed">>, []};
all_ext(<<"bh2">>) -> {<<"application">>, <<"vnd.fujitsu.oasysprs">>, []};
all_ext(<<"bin">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"blb">>) -> {<<"application">>, <<"x-blorb">>, []};
all_ext(<<"blorb">>) -> {<<"application">>, <<"x-blorb">>, []};
all_ext(<<"bmi">>) -> {<<"application">>, <<"vnd.bmi">>, []};
all_ext(<<"bmp">>) -> {<<"image">>, <<"bmp">>, []};
all_ext(<<"book">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
all_ext(<<"box">>) -> {<<"application">>, <<"vnd.previewsystems.box">>, []};
all_ext(<<"boz">>) -> {<<"application">>, <<"x-bzip2">>, []};
all_ext(<<"bpk">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"btif">>) -> {<<"image">>, <<"prs.btif">>, []};
all_ext(<<"bz2">>) -> {<<"application">>, <<"x-bzip2">>, []};
all_ext(<<"bz">>) -> {<<"application">>, <<"x-bzip">>, []};
all_ext(<<"c11amc">>) -> {<<"application">>, <<"vnd.cluetrust.cartomobile-config">>, []};
all_ext(<<"c11amz">>) -> {<<"application">>, <<"vnd.cluetrust.cartomobile-config-pkg">>, []};
all_ext(<<"c4d">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
all_ext(<<"c4f">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
all_ext(<<"c4g">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
all_ext(<<"c4p">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
all_ext(<<"c4u">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
all_ext(<<"cab">>) -> {<<"application">>, <<"vnd.ms-cab-compressed">>, []};
all_ext(<<"caf">>) -> {<<"audio">>, <<"x-caf">>, []};
all_ext(<<"cap">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
all_ext(<<"car">>) -> {<<"application">>, <<"vnd.curl.car">>, []};
all_ext(<<"cat">>) -> {<<"application">>, <<"vnd.ms-pki.seccat">>, []};
all_ext(<<"cb7">>) -> {<<"application">>, <<"x-cbr">>, []};
all_ext(<<"cba">>) -> {<<"application">>, <<"x-cbr">>, []};
all_ext(<<"cbr">>) -> {<<"application">>, <<"x-cbr">>, []};
all_ext(<<"cbt">>) -> {<<"application">>, <<"x-cbr">>, []};
all_ext(<<"cbz">>) -> {<<"application">>, <<"x-cbr">>, []};
all_ext(<<"cct">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"cc">>) -> {<<"text">>, <<"x-c">>, []};
all_ext(<<"ccxml">>) -> {<<"application">>, <<"ccxml+xml">>, []};
all_ext(<<"cdbcmsg">>) -> {<<"application">>, <<"vnd.contact.cmsg">>, []};
all_ext(<<"cdf">>) -> {<<"application">>, <<"x-netcdf">>, []};
all_ext(<<"cdkey">>) -> {<<"application">>, <<"vnd.mediastation.cdkey">>, []};
all_ext(<<"cdmia">>) -> {<<"application">>, <<"cdmi-capability">>, []};
all_ext(<<"cdmic">>) -> {<<"application">>, <<"cdmi-container">>, []};
all_ext(<<"cdmid">>) -> {<<"application">>, <<"cdmi-domain">>, []};
all_ext(<<"cdmio">>) -> {<<"application">>, <<"cdmi-object">>, []};
all_ext(<<"cdmiq">>) -> {<<"application">>, <<"cdmi-queue">>, []};
all_ext(<<"cdx">>) -> {<<"chemical">>, <<"x-cdx">>, []};
all_ext(<<"cdxml">>) -> {<<"application">>, <<"vnd.chemdraw+xml">>, []};
all_ext(<<"cdy">>) -> {<<"application">>, <<"vnd.cinderella">>, []};
all_ext(<<"cer">>) -> {<<"application">>, <<"pkix-cert">>, []};
all_ext(<<"cfs">>) -> {<<"application">>, <<"x-cfs-compressed">>, []};
all_ext(<<"cgm">>) -> {<<"image">>, <<"cgm">>, []};
all_ext(<<"chat">>) -> {<<"application">>, <<"x-chat">>, []};
all_ext(<<"chm">>) -> {<<"application">>, <<"vnd.ms-htmlhelp">>, []};
all_ext(<<"chrt">>) -> {<<"application">>, <<"vnd.kde.kchart">>, []};
all_ext(<<"cif">>) -> {<<"chemical">>, <<"x-cif">>, []};
all_ext(<<"cii">>) -> {<<"application">>, <<"vnd.anser-web-certificate-issue-initiation">>, []};
all_ext(<<"cil">>) -> {<<"application">>, <<"vnd.ms-artgalry">>, []};
all_ext(<<"cla">>) -> {<<"application">>, <<"vnd.claymore">>, []};
all_ext(<<"class">>) -> {<<"application">>, <<"java-vm">>, []};
all_ext(<<"clkk">>) -> {<<"application">>, <<"vnd.crick.clicker.keyboard">>, []};
all_ext(<<"clkp">>) -> {<<"application">>, <<"vnd.crick.clicker.palette">>, []};
all_ext(<<"clkt">>) -> {<<"application">>, <<"vnd.crick.clicker.template">>, []};
all_ext(<<"clkw">>) -> {<<"application">>, <<"vnd.crick.clicker.wordbank">>, []};
all_ext(<<"clkx">>) -> {<<"application">>, <<"vnd.crick.clicker">>, []};
all_ext(<<"clp">>) -> {<<"application">>, <<"x-msclip">>, []};
all_ext(<<"cmc">>) -> {<<"application">>, <<"vnd.cosmocaller">>, []};
all_ext(<<"cmdf">>) -> {<<"chemical">>, <<"x-cmdf">>, []};
all_ext(<<"cml">>) -> {<<"chemical">>, <<"x-cml">>, []};
all_ext(<<"cmp">>) -> {<<"application">>, <<"vnd.yellowriver-custom-menu">>, []};
all_ext(<<"cmx">>) -> {<<"image">>, <<"x-cmx">>, []};
all_ext(<<"cod">>) -> {<<"application">>, <<"vnd.rim.cod">>, []};
all_ext(<<"com">>) -> {<<"application">>, <<"x-msdownload">>, []};
all_ext(<<"conf">>) -> {<<"text">>, <<"plain">>, []};
all_ext(<<"cpio">>) -> {<<"application">>, <<"x-cpio">>, []};
all_ext(<<"cpp">>) -> {<<"text">>, <<"x-c">>, []};
all_ext(<<"cpt">>) -> {<<"application">>, <<"mac-compactpro">>, []};
all_ext(<<"crd">>) -> {<<"application">>, <<"x-mscardfile">>, []};
all_ext(<<"crl">>) -> {<<"application">>, <<"pkix-crl">>, []};
all_ext(<<"crt">>) -> {<<"application">>, <<"x-x509-ca-cert">>, []};
all_ext(<<"cryptonote">>) -> {<<"application">>, <<"vnd.rig.cryptonote">>, []};
all_ext(<<"csh">>) -> {<<"application">>, <<"x-csh">>, []};
all_ext(<<"csml">>) -> {<<"chemical">>, <<"x-csml">>, []};
all_ext(<<"csp">>) -> {<<"application">>, <<"vnd.commonspace">>, []};
all_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
all_ext(<<"cst">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"csv">>) -> {<<"text">>, <<"csv">>, []};
all_ext(<<"c">>) -> {<<"text">>, <<"x-c">>, []};
all_ext(<<"cu">>) -> {<<"application">>, <<"cu-seeme">>, []};
all_ext(<<"curl">>) -> {<<"text">>, <<"vnd.curl">>, []};
all_ext(<<"cww">>) -> {<<"application">>, <<"prs.cww">>, []};
all_ext(<<"cxt">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"cxx">>) -> {<<"text">>, <<"x-c">>, []};
all_ext(<<"dae">>) -> {<<"model">>, <<"vnd.collada+xml">>, []};
all_ext(<<"daf">>) -> {<<"application">>, <<"vnd.mobius.daf">>, []};
all_ext(<<"dart">>) -> {<<"application">>, <<"vnd.dart">>, []};
all_ext(<<"dataless">>) -> {<<"application">>, <<"vnd.fdsn.seed">>, []};
all_ext(<<"davmount">>) -> {<<"application">>, <<"davmount+xml">>, []};
all_ext(<<"dbk">>) -> {<<"application">>, <<"docbook+xml">>, []};
all_ext(<<"dcr">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"dcurl">>) -> {<<"text">>, <<"vnd.curl.dcurl">>, []};
all_ext(<<"dd2">>) -> {<<"application">>, <<"vnd.oma.dd2+xml">>, []};
all_ext(<<"ddd">>) -> {<<"application">>, <<"vnd.fujixerox.ddd">>, []};
all_ext(<<"deb">>) -> {<<"application">>, <<"x-debian-package">>, []};
all_ext(<<"def">>) -> {<<"text">>, <<"plain">>, []};
all_ext(<<"deploy">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"der">>) -> {<<"application">>, <<"x-x509-ca-cert">>, []};
all_ext(<<"dfac">>) -> {<<"application">>, <<"vnd.dreamfactory">>, []};
all_ext(<<"dgc">>) -> {<<"application">>, <<"x-dgc-compressed">>, []};
all_ext(<<"dic">>) -> {<<"text">>, <<"x-c">>, []};
all_ext(<<"dir">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"dis">>) -> {<<"application">>, <<"vnd.mobius.dis">>, []};
all_ext(<<"dist">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"distz">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"djv">>) -> {<<"image">>, <<"vnd.djvu">>, []};
all_ext(<<"djvu">>) -> {<<"image">>, <<"vnd.djvu">>, []};
all_ext(<<"dll">>) -> {<<"application">>, <<"x-msdownload">>, []};
all_ext(<<"dmg">>) -> {<<"application">>, <<"x-apple-diskimage">>, []};
all_ext(<<"dmp">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
all_ext(<<"dms">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"dna">>) -> {<<"application">>, <<"vnd.dna">>, []};
all_ext(<<"doc">>) -> {<<"application">>, <<"msword">>, []};
all_ext(<<"docm">>) -> {<<"application">>, <<"vnd.ms-word.document.macroenabled.12">>, []};
all_ext(<<"docx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.wordprocessingml.document">>, []};
all_ext(<<"dot">>) -> {<<"application">>, <<"msword">>, []};
all_ext(<<"dotm">>) -> {<<"application">>, <<"vnd.ms-word.template.macroenabled.12">>, []};
all_ext(<<"dotx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.wordprocessingml.template">>, []};
all_ext(<<"dp">>) -> {<<"application">>, <<"vnd.osgi.dp">>, []};
all_ext(<<"dpg">>) -> {<<"application">>, <<"vnd.dpgraph">>, []};
all_ext(<<"dra">>) -> {<<"audio">>, <<"vnd.dra">>, []};
all_ext(<<"dsc">>) -> {<<"text">>, <<"prs.lines.tag">>, []};
all_ext(<<"dssc">>) -> {<<"application">>, <<"dssc+der">>, []};
all_ext(<<"dtb">>) -> {<<"application">>, <<"x-dtbook+xml">>, []};
all_ext(<<"dtd">>) -> {<<"application">>, <<"xml-dtd">>, []};
all_ext(<<"dts">>) -> {<<"audio">>, <<"vnd.dts">>, []};
all_ext(<<"dtshd">>) -> {<<"audio">>, <<"vnd.dts.hd">>, []};
all_ext(<<"dump">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"dvb">>) -> {<<"video">>, <<"vnd.dvb.file">>, []};
all_ext(<<"dvi">>) -> {<<"application">>, <<"x-dvi">>, []};
all_ext(<<"dwf">>) -> {<<"model">>, <<"vnd.dwf">>, []};
all_ext(<<"dwg">>) -> {<<"image">>, <<"vnd.dwg">>, []};
all_ext(<<"dxf">>) -> {<<"image">>, <<"vnd.dxf">>, []};
all_ext(<<"dxp">>) -> {<<"application">>, <<"vnd.spotfire.dxp">>, []};
all_ext(<<"dxr">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"ecelp4800">>) -> {<<"audio">>, <<"vnd.nuera.ecelp4800">>, []};
all_ext(<<"ecelp7470">>) -> {<<"audio">>, <<"vnd.nuera.ecelp7470">>, []};
all_ext(<<"ecelp9600">>) -> {<<"audio">>, <<"vnd.nuera.ecelp9600">>, []};
all_ext(<<"ecma">>) -> {<<"application">>, <<"ecmascript">>, []};
all_ext(<<"edm">>) -> {<<"application">>, <<"vnd.novadigm.edm">>, []};
all_ext(<<"edx">>) -> {<<"application">>, <<"vnd.novadigm.edx">>, []};
all_ext(<<"efif">>) -> {<<"application">>, <<"vnd.picsel">>, []};
all_ext(<<"ei6">>) -> {<<"application">>, <<"vnd.pg.osasli">>, []};
all_ext(<<"elc">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"emf">>) -> {<<"application">>, <<"x-msmetafile">>, []};
all_ext(<<"eml">>) -> {<<"message">>, <<"rfc822">>, []};
all_ext(<<"emma">>) -> {<<"application">>, <<"emma+xml">>, []};
all_ext(<<"emz">>) -> {<<"application">>, <<"x-msmetafile">>, []};
all_ext(<<"eol">>) -> {<<"audio">>, <<"vnd.digital-winds">>, []};
all_ext(<<"eot">>) -> {<<"application">>, <<"vnd.ms-fontobject">>, []};
all_ext(<<"eps">>) -> {<<"application">>, <<"postscript">>, []};
all_ext(<<"epub">>) -> {<<"application">>, <<"epub+zip">>, []};
all_ext(<<"es3">>) -> {<<"application">>, <<"vnd.eszigno3+xml">>, []};
all_ext(<<"esa">>) -> {<<"application">>, <<"vnd.osgi.subsystem">>, []};
all_ext(<<"esf">>) -> {<<"application">>, <<"vnd.epson.esf">>, []};
all_ext(<<"et3">>) -> {<<"application">>, <<"vnd.eszigno3+xml">>, []};
all_ext(<<"etx">>) -> {<<"text">>, <<"x-setext">>, []};
all_ext(<<"eva">>) -> {<<"application">>, <<"x-eva">>, []};
all_ext(<<"evy">>) -> {<<"application">>, <<"x-envoy">>, []};
all_ext(<<"exe">>) -> {<<"application">>, <<"x-msdownload">>, []};
all_ext(<<"exi">>) -> {<<"application">>, <<"exi">>, []};
all_ext(<<"ext">>) -> {<<"application">>, <<"vnd.novadigm.ext">>, []};
all_ext(<<"ez2">>) -> {<<"application">>, <<"vnd.ezpix-album">>, []};
all_ext(<<"ez3">>) -> {<<"application">>, <<"vnd.ezpix-package">>, []};
all_ext(<<"ez">>) -> {<<"application">>, <<"andrew-inset">>, []};
all_ext(<<"f4v">>) -> {<<"video">>, <<"x-f4v">>, []};
all_ext(<<"f77">>) -> {<<"text">>, <<"x-fortran">>, []};
all_ext(<<"f90">>) -> {<<"text">>, <<"x-fortran">>, []};
all_ext(<<"fbs">>) -> {<<"image">>, <<"vnd.fastbidsheet">>, []};
all_ext(<<"fcdt">>) -> {<<"application">>, <<"vnd.adobe.formscentral.fcdt">>, []};
all_ext(<<"fcs">>) -> {<<"application">>, <<"vnd.isac.fcs">>, []};
all_ext(<<"fdf">>) -> {<<"application">>, <<"vnd.fdf">>, []};
all_ext(<<"fe_launch">>) -> {<<"application">>, <<"vnd.denovo.fcselayout-link">>, []};
all_ext(<<"fg5">>) -> {<<"application">>, <<"vnd.fujitsu.oasysgp">>, []};
all_ext(<<"fgd">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"fh4">>) -> {<<"image">>, <<"x-freehand">>, []};
all_ext(<<"fh5">>) -> {<<"image">>, <<"x-freehand">>, []};
all_ext(<<"fh7">>) -> {<<"image">>, <<"x-freehand">>, []};
all_ext(<<"fhc">>) -> {<<"image">>, <<"x-freehand">>, []};
all_ext(<<"fh">>) -> {<<"image">>, <<"x-freehand">>, []};
all_ext(<<"fig">>) -> {<<"application">>, <<"x-xfig">>, []};
all_ext(<<"flac">>) -> {<<"audio">>, <<"x-flac">>, []};
all_ext(<<"fli">>) -> {<<"video">>, <<"x-fli">>, []};
all_ext(<<"flo">>) -> {<<"application">>, <<"vnd.micrografx.flo">>, []};
all_ext(<<"flv">>) -> {<<"video">>, <<"x-flv">>, []};
all_ext(<<"flw">>) -> {<<"application">>, <<"vnd.kde.kivio">>, []};
all_ext(<<"flx">>) -> {<<"text">>, <<"vnd.fmi.flexstor">>, []};
all_ext(<<"fly">>) -> {<<"text">>, <<"vnd.fly">>, []};
all_ext(<<"fm">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
all_ext(<<"fnc">>) -> {<<"application">>, <<"vnd.frogans.fnc">>, []};
all_ext(<<"for">>) -> {<<"text">>, <<"x-fortran">>, []};
all_ext(<<"fpx">>) -> {<<"image">>, <<"vnd.fpx">>, []};
all_ext(<<"frame">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
all_ext(<<"fsc">>) -> {<<"application">>, <<"vnd.fsc.weblaunch">>, []};
all_ext(<<"fst">>) -> {<<"image">>, <<"vnd.fst">>, []};
all_ext(<<"ftc">>) -> {<<"application">>, <<"vnd.fluxtime.clip">>, []};
all_ext(<<"f">>) -> {<<"text">>, <<"x-fortran">>, []};
all_ext(<<"fti">>) -> {<<"application">>, <<"vnd.anser-web-funds-transfer-initiation">>, []};
all_ext(<<"fvt">>) -> {<<"video">>, <<"vnd.fvt">>, []};
all_ext(<<"fxp">>) -> {<<"application">>, <<"vnd.adobe.fxp">>, []};
all_ext(<<"fxpl">>) -> {<<"application">>, <<"vnd.adobe.fxp">>, []};
all_ext(<<"fzs">>) -> {<<"application">>, <<"vnd.fuzzysheet">>, []};
all_ext(<<"g2w">>) -> {<<"application">>, <<"vnd.geoplan">>, []};
all_ext(<<"g3">>) -> {<<"image">>, <<"g3fax">>, []};
all_ext(<<"g3w">>) -> {<<"application">>, <<"vnd.geospace">>, []};
all_ext(<<"gac">>) -> {<<"application">>, <<"vnd.groove-account">>, []};
all_ext(<<"gam">>) -> {<<"application">>, <<"x-tads">>, []};
all_ext(<<"gbr">>) -> {<<"application">>, <<"rpki-ghostbusters">>, []};
all_ext(<<"gca">>) -> {<<"application">>, <<"x-gca-compressed">>, []};
all_ext(<<"gdl">>) -> {<<"model">>, <<"vnd.gdl">>, []};
all_ext(<<"geo">>) -> {<<"application">>, <<"vnd.dynageo">>, []};
all_ext(<<"gex">>) -> {<<"application">>, <<"vnd.geometry-explorer">>, []};
all_ext(<<"ggb">>) -> {<<"application">>, <<"vnd.geogebra.file">>, []};
all_ext(<<"ggt">>) -> {<<"application">>, <<"vnd.geogebra.tool">>, []};
all_ext(<<"ghf">>) -> {<<"application">>, <<"vnd.groove-help">>, []};
all_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
all_ext(<<"gim">>) -> {<<"application">>, <<"vnd.groove-identity-message">>, []};
all_ext(<<"gml">>) -> {<<"application">>, <<"gml+xml">>, []};
all_ext(<<"gmx">>) -> {<<"application">>, <<"vnd.gmx">>, []};
all_ext(<<"gnumeric">>) -> {<<"application">>, <<"x-gnumeric">>, []};
all_ext(<<"gph">>) -> {<<"application">>, <<"vnd.flographit">>, []};
all_ext(<<"gpx">>) -> {<<"application">>, <<"gpx+xml">>, []};
all_ext(<<"gqf">>) -> {<<"application">>, <<"vnd.grafeq">>, []};
all_ext(<<"gqs">>) -> {<<"application">>, <<"vnd.grafeq">>, []};
all_ext(<<"gram">>) -> {<<"application">>, <<"srgs">>, []};
all_ext(<<"gramps">>) -> {<<"application">>, <<"x-gramps-xml">>, []};
all_ext(<<"gre">>) -> {<<"application">>, <<"vnd.geometry-explorer">>, []};
all_ext(<<"grv">>) -> {<<"application">>, <<"vnd.groove-injector">>, []};
all_ext(<<"grxml">>) -> {<<"application">>, <<"srgs+xml">>, []};
all_ext(<<"gsf">>) -> {<<"application">>, <<"x-font-ghostscript">>, []};
all_ext(<<"gtar">>) -> {<<"application">>, <<"x-gtar">>, []};
all_ext(<<"gtm">>) -> {<<"application">>, <<"vnd.groove-tool-message">>, []};
all_ext(<<"gtw">>) -> {<<"model">>, <<"vnd.gtw">>, []};
all_ext(<<"gv">>) -> {<<"text">>, <<"vnd.graphviz">>, []};
all_ext(<<"gxf">>) -> {<<"application">>, <<"gxf">>, []};
all_ext(<<"gxt">>) -> {<<"application">>, <<"vnd.geonext">>, []};
all_ext(<<"h261">>) -> {<<"video">>, <<"h261">>, []};
all_ext(<<"h263">>) -> {<<"video">>, <<"h263">>, []};
all_ext(<<"h264">>) -> {<<"video">>, <<"h264">>, []};
all_ext(<<"hal">>) -> {<<"application">>, <<"vnd.hal+xml">>, []};
all_ext(<<"hbci">>) -> {<<"application">>, <<"vnd.hbci">>, []};
all_ext(<<"hdf">>) -> {<<"application">>, <<"x-hdf">>, []};
all_ext(<<"hh">>) -> {<<"text">>, <<"x-c">>, []};
all_ext(<<"hlp">>) -> {<<"application">>, <<"winhlp">>, []};
all_ext(<<"hpgl">>) -> {<<"application">>, <<"vnd.hp-hpgl">>, []};
all_ext(<<"hpid">>) -> {<<"application">>, <<"vnd.hp-hpid">>, []};
all_ext(<<"hps">>) -> {<<"application">>, <<"vnd.hp-hps">>, []};
all_ext(<<"hqx">>) -> {<<"application">>, <<"mac-binhex40">>, []};
all_ext(<<"h">>) -> {<<"text">>, <<"x-c">>, []};
all_ext(<<"htke">>) -> {<<"application">>, <<"vnd.kenameaapp">>, []};
all_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
all_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
all_ext(<<"hvd">>) -> {<<"application">>, <<"vnd.yamaha.hv-dic">>, []};
all_ext(<<"hvp">>) -> {<<"application">>, <<"vnd.yamaha.hv-voice">>, []};
all_ext(<<"hvs">>) -> {<<"application">>, <<"vnd.yamaha.hv-script">>, []};
all_ext(<<"i2g">>) -> {<<"application">>, <<"vnd.intergeo">>, []};
all_ext(<<"icc">>) -> {<<"application">>, <<"vnd.iccprofile">>, []};
all_ext(<<"ice">>) -> {<<"x-conference">>, <<"x-cooltalk">>, []};
all_ext(<<"icm">>) -> {<<"application">>, <<"vnd.iccprofile">>, []};
all_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
all_ext(<<"ics">>) -> {<<"text">>, <<"calendar">>, []};
all_ext(<<"ief">>) -> {<<"image">>, <<"ief">>, []};
all_ext(<<"ifb">>) -> {<<"text">>, <<"calendar">>, []};
all_ext(<<"ifm">>) -> {<<"application">>, <<"vnd.shana.informed.formdata">>, []};
all_ext(<<"iges">>) -> {<<"model">>, <<"iges">>, []};
all_ext(<<"igl">>) -> {<<"application">>, <<"vnd.igloader">>, []};
all_ext(<<"igm">>) -> {<<"application">>, <<"vnd.insors.igm">>, []};
all_ext(<<"igs">>) -> {<<"model">>, <<"iges">>, []};
all_ext(<<"igx">>) -> {<<"application">>, <<"vnd.micrografx.igx">>, []};
all_ext(<<"iif">>) -> {<<"application">>, <<"vnd.shana.informed.interchange">>, []};
all_ext(<<"imp">>) -> {<<"application">>, <<"vnd.accpac.simply.imp">>, []};
all_ext(<<"ims">>) -> {<<"application">>, <<"vnd.ms-ims">>, []};
all_ext(<<"ink">>) -> {<<"application">>, <<"inkml+xml">>, []};
all_ext(<<"inkml">>) -> {<<"application">>, <<"inkml+xml">>, []};
all_ext(<<"install">>) -> {<<"application">>, <<"x-install-instructions">>, []};
all_ext(<<"in">>) -> {<<"text">>, <<"plain">>, []};
all_ext(<<"iota">>) -> {<<"application">>, <<"vnd.astraea-software.iota">>, []};
all_ext(<<"ipfix">>) -> {<<"application">>, <<"ipfix">>, []};
all_ext(<<"ipk">>) -> {<<"application">>, <<"vnd.shana.informed.package">>, []};
all_ext(<<"irm">>) -> {<<"application">>, <<"vnd.ibm.rights-management">>, []};
all_ext(<<"irp">>) -> {<<"application">>, <<"vnd.irepository.package+xml">>, []};
all_ext(<<"iso">>) -> {<<"application">>, <<"x-iso9660-image">>, []};
all_ext(<<"itp">>) -> {<<"application">>, <<"vnd.shana.informed.formtemplate">>, []};
all_ext(<<"ivp">>) -> {<<"application">>, <<"vnd.immervision-ivp">>, []};
all_ext(<<"ivu">>) -> {<<"application">>, <<"vnd.immervision-ivu">>, []};
all_ext(<<"jad">>) -> {<<"text">>, <<"vnd.sun.j2me.app-descriptor">>, []};
all_ext(<<"jam">>) -> {<<"application">>, <<"vnd.jam">>, []};
all_ext(<<"jar">>) -> {<<"application">>, <<"java-archive">>, []};
all_ext(<<"java">>) -> {<<"text">>, <<"x-java-source">>, []};
all_ext(<<"jisp">>) -> {<<"application">>, <<"vnd.jisp">>, []};
all_ext(<<"jlt">>) -> {<<"application">>, <<"vnd.hp-jlyt">>, []};
all_ext(<<"jnlp">>) -> {<<"application">>, <<"x-java-jnlp-file">>, []};
all_ext(<<"joda">>) -> {<<"application">>, <<"vnd.joost.joda-archive">>, []};
all_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
all_ext(<<"jpe">>) -> {<<"image">>, <<"jpeg">>, []};
all_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
all_ext(<<"jpgm">>) -> {<<"video">>, <<"jpm">>, []};
all_ext(<<"jpgv">>) -> {<<"video">>, <<"jpeg">>, []};
all_ext(<<"jpm">>) -> {<<"video">>, <<"jpm">>, []};
all_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
all_ext(<<"json">>) -> {<<"application">>, <<"json">>, []};
all_ext(<<"jsonml">>) -> {<<"application">>, <<"jsonml+json">>, []};
all_ext(<<"kar">>) -> {<<"audio">>, <<"midi">>, []};
all_ext(<<"karbon">>) -> {<<"application">>, <<"vnd.kde.karbon">>, []};
all_ext(<<"kfo">>) -> {<<"application">>, <<"vnd.kde.kformula">>, []};
all_ext(<<"kia">>) -> {<<"application">>, <<"vnd.kidspiration">>, []};
all_ext(<<"kml">>) -> {<<"application">>, <<"vnd.google-earth.kml+xml">>, []};
all_ext(<<"kmz">>) -> {<<"application">>, <<"vnd.google-earth.kmz">>, []};
all_ext(<<"kne">>) -> {<<"application">>, <<"vnd.kinar">>, []};
all_ext(<<"knp">>) -> {<<"application">>, <<"vnd.kinar">>, []};
all_ext(<<"kon">>) -> {<<"application">>, <<"vnd.kde.kontour">>, []};
all_ext(<<"kpr">>) -> {<<"application">>, <<"vnd.kde.kpresenter">>, []};
all_ext(<<"kpt">>) -> {<<"application">>, <<"vnd.kde.kpresenter">>, []};
all_ext(<<"kpxx">>) -> {<<"application">>, <<"vnd.ds-keypoint">>, []};
all_ext(<<"ksp">>) -> {<<"application">>, <<"vnd.kde.kspread">>, []};
all_ext(<<"ktr">>) -> {<<"application">>, <<"vnd.kahootz">>, []};
all_ext(<<"ktx">>) -> {<<"image">>, <<"ktx">>, []};
all_ext(<<"ktz">>) -> {<<"application">>, <<"vnd.kahootz">>, []};
all_ext(<<"kwd">>) -> {<<"application">>, <<"vnd.kde.kword">>, []};
all_ext(<<"kwt">>) -> {<<"application">>, <<"vnd.kde.kword">>, []};
all_ext(<<"lasxml">>) -> {<<"application">>, <<"vnd.las.las+xml">>, []};
all_ext(<<"latex">>) -> {<<"application">>, <<"x-latex">>, []};
all_ext(<<"lbd">>) -> {<<"application">>, <<"vnd.llamagraphics.life-balance.desktop">>, []};
all_ext(<<"lbe">>) -> {<<"application">>, <<"vnd.llamagraphics.life-balance.exchange+xml">>, []};
all_ext(<<"les">>) -> {<<"application">>, <<"vnd.hhe.lesson-player">>, []};
all_ext(<<"lha">>) -> {<<"application">>, <<"x-lzh-compressed">>, []};
all_ext(<<"link66">>) -> {<<"application">>, <<"vnd.route66.link66+xml">>, []};
all_ext(<<"list3820">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
all_ext(<<"listafp">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
all_ext(<<"list">>) -> {<<"text">>, <<"plain">>, []};
all_ext(<<"lnk">>) -> {<<"application">>, <<"x-ms-shortcut">>, []};
all_ext(<<"log">>) -> {<<"text">>, <<"plain">>, []};
all_ext(<<"lostxml">>) -> {<<"application">>, <<"lost+xml">>, []};
all_ext(<<"lrf">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"lrm">>) -> {<<"application">>, <<"vnd.ms-lrm">>, []};
all_ext(<<"ltf">>) -> {<<"application">>, <<"vnd.frogans.ltf">>, []};
all_ext(<<"lvp">>) -> {<<"audio">>, <<"vnd.lucent.voice">>, []};
all_ext(<<"lwp">>) -> {<<"application">>, <<"vnd.lotus-wordpro">>, []};
all_ext(<<"lzh">>) -> {<<"application">>, <<"x-lzh-compressed">>, []};
all_ext(<<"m13">>) -> {<<"application">>, <<"x-msmediaview">>, []};
all_ext(<<"m14">>) -> {<<"application">>, <<"x-msmediaview">>, []};
all_ext(<<"m1v">>) -> {<<"video">>, <<"mpeg">>, []};
all_ext(<<"m21">>) -> {<<"application">>, <<"mp21">>, []};
all_ext(<<"m2a">>) -> {<<"audio">>, <<"mpeg">>, []};
all_ext(<<"m2v">>) -> {<<"video">>, <<"mpeg">>, []};
all_ext(<<"m3a">>) -> {<<"audio">>, <<"mpeg">>, []};
all_ext(<<"m3u8">>) -> {<<"application">>, <<"vnd.apple.mpegurl">>, []};
all_ext(<<"m3u">>) -> {<<"audio">>, <<"x-mpegurl">>, []};
all_ext(<<"m4u">>) -> {<<"video">>, <<"vnd.mpegurl">>, []};
all_ext(<<"m4v">>) -> {<<"video">>, <<"x-m4v">>, []};
all_ext(<<"ma">>) -> {<<"application">>, <<"mathematica">>, []};
all_ext(<<"mads">>) -> {<<"application">>, <<"mads+xml">>, []};
all_ext(<<"mag">>) -> {<<"application">>, <<"vnd.ecowin.chart">>, []};
all_ext(<<"maker">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
all_ext(<<"man">>) -> {<<"text">>, <<"troff">>, []};
all_ext(<<"mar">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"mathml">>) -> {<<"application">>, <<"mathml+xml">>, []};
all_ext(<<"mb">>) -> {<<"application">>, <<"mathematica">>, []};
all_ext(<<"mbk">>) -> {<<"application">>, <<"vnd.mobius.mbk">>, []};
all_ext(<<"mbox">>) -> {<<"application">>, <<"mbox">>, []};
all_ext(<<"mc1">>) -> {<<"application">>, <<"vnd.medcalcdata">>, []};
all_ext(<<"mcd">>) -> {<<"application">>, <<"vnd.mcd">>, []};
all_ext(<<"mcurl">>) -> {<<"text">>, <<"vnd.curl.mcurl">>, []};
all_ext(<<"mdb">>) -> {<<"application">>, <<"x-msaccess">>, []};
all_ext(<<"mdi">>) -> {<<"image">>, <<"vnd.ms-modi">>, []};
all_ext(<<"mesh">>) -> {<<"model">>, <<"mesh">>, []};
all_ext(<<"meta4">>) -> {<<"application">>, <<"metalink4+xml">>, []};
all_ext(<<"metalink">>) -> {<<"application">>, <<"metalink+xml">>, []};
all_ext(<<"me">>) -> {<<"text">>, <<"troff">>, []};
all_ext(<<"mets">>) -> {<<"application">>, <<"mets+xml">>, []};
all_ext(<<"mfm">>) -> {<<"application">>, <<"vnd.mfmp">>, []};
all_ext(<<"mft">>) -> {<<"application">>, <<"rpki-manifest">>, []};
all_ext(<<"mgp">>) -> {<<"application">>, <<"vnd.osgeo.mapguide.package">>, []};
all_ext(<<"mgz">>) -> {<<"application">>, <<"vnd.proteus.magazine">>, []};
all_ext(<<"mid">>) -> {<<"audio">>, <<"midi">>, []};
all_ext(<<"midi">>) -> {<<"audio">>, <<"midi">>, []};
all_ext(<<"mie">>) -> {<<"application">>, <<"x-mie">>, []};
all_ext(<<"mif">>) -> {<<"application">>, <<"vnd.mif">>, []};
all_ext(<<"mime">>) -> {<<"message">>, <<"rfc822">>, []};
all_ext(<<"mj2">>) -> {<<"video">>, <<"mj2">>, []};
all_ext(<<"mjp2">>) -> {<<"video">>, <<"mj2">>, []};
all_ext(<<"mk3d">>) -> {<<"video">>, <<"x-matroska">>, []};
all_ext(<<"mka">>) -> {<<"audio">>, <<"x-matroska">>, []};
all_ext(<<"mks">>) -> {<<"video">>, <<"x-matroska">>, []};
all_ext(<<"mkv">>) -> {<<"video">>, <<"x-matroska">>, []};
all_ext(<<"mlp">>) -> {<<"application">>, <<"vnd.dolby.mlp">>, []};
all_ext(<<"mmd">>) -> {<<"application">>, <<"vnd.chipnuts.karaoke-mmd">>, []};
all_ext(<<"mmf">>) -> {<<"application">>, <<"vnd.smaf">>, []};
all_ext(<<"mmr">>) -> {<<"image">>, <<"vnd.fujixerox.edmics-mmr">>, []};
all_ext(<<"mng">>) -> {<<"video">>, <<"x-mng">>, []};
all_ext(<<"mny">>) -> {<<"application">>, <<"x-msmoney">>, []};
all_ext(<<"mobi">>) -> {<<"application">>, <<"x-mobipocket-ebook">>, []};
all_ext(<<"mods">>) -> {<<"application">>, <<"mods+xml">>, []};
all_ext(<<"movie">>) -> {<<"video">>, <<"x-sgi-movie">>, []};
all_ext(<<"mov">>) -> {<<"video">>, <<"quicktime">>, []};
all_ext(<<"mp21">>) -> {<<"application">>, <<"mp21">>, []};
all_ext(<<"mp2a">>) -> {<<"audio">>, <<"mpeg">>, []};
all_ext(<<"mp2">>) -> {<<"audio">>, <<"mpeg">>, []};
all_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
all_ext(<<"mp4a">>) -> {<<"audio">>, <<"mp4">>, []};
all_ext(<<"mp4s">>) -> {<<"application">>, <<"mp4">>, []};
all_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
all_ext(<<"mp4v">>) -> {<<"video">>, <<"mp4">>, []};
all_ext(<<"mpc">>) -> {<<"application">>, <<"vnd.mophun.certificate">>, []};
all_ext(<<"mpeg">>) -> {<<"video">>, <<"mpeg">>, []};
all_ext(<<"mpe">>) -> {<<"video">>, <<"mpeg">>, []};
all_ext(<<"mpg4">>) -> {<<"video">>, <<"mp4">>, []};
all_ext(<<"mpga">>) -> {<<"audio">>, <<"mpeg">>, []};
all_ext(<<"mpg">>) -> {<<"video">>, <<"mpeg">>, []};
all_ext(<<"mpkg">>) -> {<<"application">>, <<"vnd.apple.installer+xml">>, []};
all_ext(<<"mpm">>) -> {<<"application">>, <<"vnd.blueice.multipass">>, []};
all_ext(<<"mpn">>) -> {<<"application">>, <<"vnd.mophun.application">>, []};
all_ext(<<"mpp">>) -> {<<"application">>, <<"vnd.ms-project">>, []};
all_ext(<<"mpt">>) -> {<<"application">>, <<"vnd.ms-project">>, []};
all_ext(<<"mpy">>) -> {<<"application">>, <<"vnd.ibm.minipay">>, []};
all_ext(<<"mqy">>) -> {<<"application">>, <<"vnd.mobius.mqy">>, []};
all_ext(<<"mrc">>) -> {<<"application">>, <<"marc">>, []};
all_ext(<<"mrcx">>) -> {<<"application">>, <<"marcxml+xml">>, []};
all_ext(<<"mscml">>) -> {<<"application">>, <<"mediaservercontrol+xml">>, []};
all_ext(<<"mseed">>) -> {<<"application">>, <<"vnd.fdsn.mseed">>, []};
all_ext(<<"mseq">>) -> {<<"application">>, <<"vnd.mseq">>, []};
all_ext(<<"msf">>) -> {<<"application">>, <<"vnd.epson.msf">>, []};
all_ext(<<"msh">>) -> {<<"model">>, <<"mesh">>, []};
all_ext(<<"msi">>) -> {<<"application">>, <<"x-msdownload">>, []};
all_ext(<<"msl">>) -> {<<"application">>, <<"vnd.mobius.msl">>, []};
all_ext(<<"ms">>) -> {<<"text">>, <<"troff">>, []};
all_ext(<<"msty">>) -> {<<"application">>, <<"vnd.muvee.style">>, []};
all_ext(<<"mts">>) -> {<<"model">>, <<"vnd.mts">>, []};
all_ext(<<"mus">>) -> {<<"application">>, <<"vnd.musician">>, []};
all_ext(<<"musicxml">>) -> {<<"application">>, <<"vnd.recordare.musicxml+xml">>, []};
all_ext(<<"mvb">>) -> {<<"application">>, <<"x-msmediaview">>, []};
all_ext(<<"mwf">>) -> {<<"application">>, <<"vnd.mfer">>, []};
all_ext(<<"mxf">>) -> {<<"application">>, <<"mxf">>, []};
all_ext(<<"mxl">>) -> {<<"application">>, <<"vnd.recordare.musicxml">>, []};
all_ext(<<"mxml">>) -> {<<"application">>, <<"xv+xml">>, []};
all_ext(<<"mxs">>) -> {<<"application">>, <<"vnd.triscape.mxs">>, []};
all_ext(<<"mxu">>) -> {<<"video">>, <<"vnd.mpegurl">>, []};
all_ext(<<"n3">>) -> {<<"text">>, <<"n3">>, []};
all_ext(<<"nb">>) -> {<<"application">>, <<"mathematica">>, []};
all_ext(<<"nbp">>) -> {<<"application">>, <<"vnd.wolfram.player">>, []};
all_ext(<<"nc">>) -> {<<"application">>, <<"x-netcdf">>, []};
all_ext(<<"ncx">>) -> {<<"application">>, <<"x-dtbncx+xml">>, []};
all_ext(<<"nfo">>) -> {<<"text">>, <<"x-nfo">>, []};
all_ext(<<"n-gage">>) -> {<<"application">>, <<"vnd.nokia.n-gage.symbian.install">>, []};
all_ext(<<"ngdat">>) -> {<<"application">>, <<"vnd.nokia.n-gage.data">>, []};
all_ext(<<"nitf">>) -> {<<"application">>, <<"vnd.nitf">>, []};
all_ext(<<"nlu">>) -> {<<"application">>, <<"vnd.neurolanguage.nlu">>, []};
all_ext(<<"nml">>) -> {<<"application">>, <<"vnd.enliven">>, []};
all_ext(<<"nnd">>) -> {<<"application">>, <<"vnd.noblenet-directory">>, []};
all_ext(<<"nns">>) -> {<<"application">>, <<"vnd.noblenet-sealer">>, []};
all_ext(<<"nnw">>) -> {<<"application">>, <<"vnd.noblenet-web">>, []};
all_ext(<<"npx">>) -> {<<"image">>, <<"vnd.net-fpx">>, []};
all_ext(<<"nsc">>) -> {<<"application">>, <<"x-conference">>, []};
all_ext(<<"nsf">>) -> {<<"application">>, <<"vnd.lotus-notes">>, []};
all_ext(<<"ntf">>) -> {<<"application">>, <<"vnd.nitf">>, []};
all_ext(<<"nzb">>) -> {<<"application">>, <<"x-nzb">>, []};
all_ext(<<"oa2">>) -> {<<"application">>, <<"vnd.fujitsu.oasys2">>, []};
all_ext(<<"oa3">>) -> {<<"application">>, <<"vnd.fujitsu.oasys3">>, []};
all_ext(<<"oas">>) -> {<<"application">>, <<"vnd.fujitsu.oasys">>, []};
all_ext(<<"obd">>) -> {<<"application">>, <<"x-msbinder">>, []};
all_ext(<<"obj">>) -> {<<"application">>, <<"x-tgif">>, []};
all_ext(<<"oda">>) -> {<<"application">>, <<"oda">>, []};
all_ext(<<"odb">>) -> {<<"application">>, <<"vnd.oasis.opendocument.database">>, []};
all_ext(<<"odc">>) -> {<<"application">>, <<"vnd.oasis.opendocument.chart">>, []};
all_ext(<<"odf">>) -> {<<"application">>, <<"vnd.oasis.opendocument.formula">>, []};
all_ext(<<"odft">>) -> {<<"application">>, <<"vnd.oasis.opendocument.formula-template">>, []};
all_ext(<<"odg">>) -> {<<"application">>, <<"vnd.oasis.opendocument.graphics">>, []};
all_ext(<<"odi">>) -> {<<"application">>, <<"vnd.oasis.opendocument.image">>, []};
all_ext(<<"odm">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-master">>, []};
all_ext(<<"odp">>) -> {<<"application">>, <<"vnd.oasis.opendocument.presentation">>, []};
all_ext(<<"ods">>) -> {<<"application">>, <<"vnd.oasis.opendocument.spreadsheet">>, []};
all_ext(<<"odt">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text">>, []};
all_ext(<<"oga">>) -> {<<"audio">>, <<"ogg">>, []};
all_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
all_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
all_ext(<<"ogx">>) -> {<<"application">>, <<"ogg">>, []};
all_ext(<<"omdoc">>) -> {<<"application">>, <<"omdoc+xml">>, []};
all_ext(<<"onepkg">>) -> {<<"application">>, <<"onenote">>, []};
all_ext(<<"onetmp">>) -> {<<"application">>, <<"onenote">>, []};
all_ext(<<"onetoc2">>) -> {<<"application">>, <<"onenote">>, []};
all_ext(<<"onetoc">>) -> {<<"application">>, <<"onenote">>, []};
all_ext(<<"opf">>) -> {<<"application">>, <<"oebps-package+xml">>, []};
all_ext(<<"opml">>) -> {<<"text">>, <<"x-opml">>, []};
all_ext(<<"oprc">>) -> {<<"application">>, <<"vnd.palm">>, []};
all_ext(<<"org">>) -> {<<"application">>, <<"vnd.lotus-organizer">>, []};
all_ext(<<"osf">>) -> {<<"application">>, <<"vnd.yamaha.openscoreformat">>, []};
all_ext(<<"osfpvg">>) -> {<<"application">>, <<"vnd.yamaha.openscoreformat.osfpvg+xml">>, []};
all_ext(<<"otc">>) -> {<<"application">>, <<"vnd.oasis.opendocument.chart-template">>, []};
all_ext(<<"otf">>) -> {<<"application">>, <<"x-font-otf">>, []};
all_ext(<<"otg">>) -> {<<"application">>, <<"vnd.oasis.opendocument.graphics-template">>, []};
all_ext(<<"oth">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-web">>, []};
all_ext(<<"oti">>) -> {<<"application">>, <<"vnd.oasis.opendocument.image-template">>, []};
all_ext(<<"otp">>) -> {<<"application">>, <<"vnd.oasis.opendocument.presentation-template">>, []};
all_ext(<<"ots">>) -> {<<"application">>, <<"vnd.oasis.opendocument.spreadsheet-template">>, []};
all_ext(<<"ott">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-template">>, []};
all_ext(<<"oxps">>) -> {<<"application">>, <<"oxps">>, []};
all_ext(<<"oxt">>) -> {<<"application">>, <<"vnd.openofficeorg.extension">>, []};
all_ext(<<"p10">>) -> {<<"application">>, <<"pkcs10">>, []};
all_ext(<<"p12">>) -> {<<"application">>, <<"x-pkcs12">>, []};
all_ext(<<"p7b">>) -> {<<"application">>, <<"x-pkcs7-certificates">>, []};
all_ext(<<"p7c">>) -> {<<"application">>, <<"pkcs7-mime">>, []};
all_ext(<<"p7m">>) -> {<<"application">>, <<"pkcs7-mime">>, []};
all_ext(<<"p7r">>) -> {<<"application">>, <<"x-pkcs7-certreqresp">>, []};
all_ext(<<"p7s">>) -> {<<"application">>, <<"pkcs7-signature">>, []};
all_ext(<<"p8">>) -> {<<"application">>, <<"pkcs8">>, []};
all_ext(<<"pas">>) -> {<<"text">>, <<"x-pascal">>, []};
all_ext(<<"paw">>) -> {<<"application">>, <<"vnd.pawaafile">>, []};
all_ext(<<"pbd">>) -> {<<"application">>, <<"vnd.powerbuilder6">>, []};
all_ext(<<"pbm">>) -> {<<"image">>, <<"x-portable-bitmap">>, []};
all_ext(<<"pcap">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
all_ext(<<"pcf">>) -> {<<"application">>, <<"x-font-pcf">>, []};
all_ext(<<"pcl">>) -> {<<"application">>, <<"vnd.hp-pcl">>, []};
all_ext(<<"pclxl">>) -> {<<"application">>, <<"vnd.hp-pclxl">>, []};
all_ext(<<"pct">>) -> {<<"image">>, <<"x-pict">>, []};
all_ext(<<"pcurl">>) -> {<<"application">>, <<"vnd.curl.pcurl">>, []};
all_ext(<<"pcx">>) -> {<<"image">>, <<"x-pcx">>, []};
all_ext(<<"pdb">>) -> {<<"application">>, <<"vnd.palm">>, []};
all_ext(<<"pdf">>) -> {<<"application">>, <<"pdf">>, []};
all_ext(<<"pfa">>) -> {<<"application">>, <<"x-font-type1">>, []};
all_ext(<<"pfb">>) -> {<<"application">>, <<"x-font-type1">>, []};
all_ext(<<"pfm">>) -> {<<"application">>, <<"x-font-type1">>, []};
all_ext(<<"pfr">>) -> {<<"application">>, <<"font-tdpfr">>, []};
all_ext(<<"pfx">>) -> {<<"application">>, <<"x-pkcs12">>, []};
all_ext(<<"pgm">>) -> {<<"image">>, <<"x-portable-graymap">>, []};
all_ext(<<"pgn">>) -> {<<"application">>, <<"x-chess-pgn">>, []};
all_ext(<<"pgp">>) -> {<<"application">>, <<"pgp-encrypted">>, []};
all_ext(<<"pic">>) -> {<<"image">>, <<"x-pict">>, []};
all_ext(<<"pkg">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"pki">>) -> {<<"application">>, <<"pkixcmp">>, []};
all_ext(<<"pkipath">>) -> {<<"application">>, <<"pkix-pkipath">>, []};
all_ext(<<"plb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-large">>, []};
all_ext(<<"plc">>) -> {<<"application">>, <<"vnd.mobius.plc">>, []};
all_ext(<<"plf">>) -> {<<"application">>, <<"vnd.pocketlearn">>, []};
all_ext(<<"pls">>) -> {<<"application">>, <<"pls+xml">>, []};
all_ext(<<"pml">>) -> {<<"application">>, <<"vnd.ctc-posml">>, []};
all_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
all_ext(<<"pnm">>) -> {<<"image">>, <<"x-portable-anymap">>, []};
all_ext(<<"portpkg">>) -> {<<"application">>, <<"vnd.macports.portpkg">>, []};
all_ext(<<"pot">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
all_ext(<<"potm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.template.macroenabled.12">>, []};
all_ext(<<"potx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.template">>, []};
all_ext(<<"ppam">>) -> {<<"application">>, <<"vnd.ms-powerpoint.addin.macroenabled.12">>, []};
all_ext(<<"ppd">>) -> {<<"application">>, <<"vnd.cups-ppd">>, []};
all_ext(<<"ppm">>) -> {<<"image">>, <<"x-portable-pixmap">>, []};
all_ext(<<"pps">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
all_ext(<<"ppsm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.slideshow.macroenabled.12">>, []};
all_ext(<<"ppsx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.slideshow">>, []};
all_ext(<<"ppt">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
all_ext(<<"pptm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.presentation.macroenabled.12">>, []};
all_ext(<<"pptx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.presentation">>, []};
all_ext(<<"pqa">>) -> {<<"application">>, <<"vnd.palm">>, []};
all_ext(<<"prc">>) -> {<<"application">>, <<"x-mobipocket-ebook">>, []};
all_ext(<<"pre">>) -> {<<"application">>, <<"vnd.lotus-freelance">>, []};
all_ext(<<"prf">>) -> {<<"application">>, <<"pics-rules">>, []};
all_ext(<<"ps">>) -> {<<"application">>, <<"postscript">>, []};
all_ext(<<"psb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-small">>, []};
all_ext(<<"psd">>) -> {<<"image">>, <<"vnd.adobe.photoshop">>, []};
all_ext(<<"psf">>) -> {<<"application">>, <<"x-font-linux-psf">>, []};
all_ext(<<"pskcxml">>) -> {<<"application">>, <<"pskc+xml">>, []};
all_ext(<<"p">>) -> {<<"text">>, <<"x-pascal">>, []};
all_ext(<<"ptid">>) -> {<<"application">>, <<"vnd.pvi.ptid1">>, []};
all_ext(<<"pub">>) -> {<<"application">>, <<"x-mspublisher">>, []};
all_ext(<<"pvb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-var">>, []};
all_ext(<<"pwn">>) -> {<<"application">>, <<"vnd.3m.post-it-notes">>, []};
all_ext(<<"pya">>) -> {<<"audio">>, <<"vnd.ms-playready.media.pya">>, []};
all_ext(<<"pyv">>) -> {<<"video">>, <<"vnd.ms-playready.media.pyv">>, []};
all_ext(<<"qam">>) -> {<<"application">>, <<"vnd.epson.quickanime">>, []};
all_ext(<<"qbo">>) -> {<<"application">>, <<"vnd.intu.qbo">>, []};
all_ext(<<"qfx">>) -> {<<"application">>, <<"vnd.intu.qfx">>, []};
all_ext(<<"qps">>) -> {<<"application">>, <<"vnd.publishare-delta-tree">>, []};
all_ext(<<"qt">>) -> {<<"video">>, <<"quicktime">>, []};
all_ext(<<"qwd">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
all_ext(<<"qwt">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
all_ext(<<"qxb">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
all_ext(<<"qxd">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
all_ext(<<"qxl">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
all_ext(<<"qxt">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
all_ext(<<"ra">>) -> {<<"audio">>, <<"x-pn-realaudio">>, []};
all_ext(<<"ram">>) -> {<<"audio">>, <<"x-pn-realaudio">>, []};
all_ext(<<"rar">>) -> {<<"application">>, <<"x-rar-compressed">>, []};
all_ext(<<"ras">>) -> {<<"image">>, <<"x-cmu-raster">>, []};
all_ext(<<"rcprofile">>) -> {<<"application">>, <<"vnd.ipunplugged.rcprofile">>, []};
all_ext(<<"rdf">>) -> {<<"application">>, <<"rdf+xml">>, []};
all_ext(<<"rdz">>) -> {<<"application">>, <<"vnd.data-vision.rdz">>, []};
all_ext(<<"rep">>) -> {<<"application">>, <<"vnd.businessobjects">>, []};
all_ext(<<"res">>) -> {<<"application">>, <<"x-dtbresource+xml">>, []};
all_ext(<<"rgb">>) -> {<<"image">>, <<"x-rgb">>, []};
all_ext(<<"rif">>) -> {<<"application">>, <<"reginfo+xml">>, []};
all_ext(<<"rip">>) -> {<<"audio">>, <<"vnd.rip">>, []};
all_ext(<<"ris">>) -> {<<"application">>, <<"x-research-info-systems">>, []};
all_ext(<<"rl">>) -> {<<"application">>, <<"resource-lists+xml">>, []};
all_ext(<<"rlc">>) -> {<<"image">>, <<"vnd.fujixerox.edmics-rlc">>, []};
all_ext(<<"rld">>) -> {<<"application">>, <<"resource-lists-diff+xml">>, []};
all_ext(<<"rm">>) -> {<<"application">>, <<"vnd.rn-realmedia">>, []};
all_ext(<<"rmi">>) -> {<<"audio">>, <<"midi">>, []};
all_ext(<<"rmp">>) -> {<<"audio">>, <<"x-pn-realaudio-plugin">>, []};
all_ext(<<"rms">>) -> {<<"application">>, <<"vnd.jcp.javame.midlet-rms">>, []};
all_ext(<<"rmvb">>) -> {<<"application">>, <<"vnd.rn-realmedia-vbr">>, []};
all_ext(<<"rnc">>) -> {<<"application">>, <<"relax-ng-compact-syntax">>, []};
all_ext(<<"roa">>) -> {<<"application">>, <<"rpki-roa">>, []};
all_ext(<<"roff">>) -> {<<"text">>, <<"troff">>, []};
all_ext(<<"rp9">>) -> {<<"application">>, <<"vnd.cloanto.rp9">>, []};
all_ext(<<"rpss">>) -> {<<"application">>, <<"vnd.nokia.radio-presets">>, []};
all_ext(<<"rpst">>) -> {<<"application">>, <<"vnd.nokia.radio-preset">>, []};
all_ext(<<"rq">>) -> {<<"application">>, <<"sparql-query">>, []};
all_ext(<<"rs">>) -> {<<"application">>, <<"rls-services+xml">>, []};
all_ext(<<"rsd">>) -> {<<"application">>, <<"rsd+xml">>, []};
all_ext(<<"rss">>) -> {<<"application">>, <<"rss+xml">>, []};
all_ext(<<"rtf">>) -> {<<"application">>, <<"rtf">>, []};
all_ext(<<"rtx">>) -> {<<"text">>, <<"richtext">>, []};
all_ext(<<"s3m">>) -> {<<"audio">>, <<"s3m">>, []};
all_ext(<<"saf">>) -> {<<"application">>, <<"vnd.yamaha.smaf-audio">>, []};
all_ext(<<"sbml">>) -> {<<"application">>, <<"sbml+xml">>, []};
all_ext(<<"sc">>) -> {<<"application">>, <<"vnd.ibm.secure-container">>, []};
all_ext(<<"scd">>) -> {<<"application">>, <<"x-msschedule">>, []};
all_ext(<<"scm">>) -> {<<"application">>, <<"vnd.lotus-screencam">>, []};
all_ext(<<"scq">>) -> {<<"application">>, <<"scvp-cv-request">>, []};
all_ext(<<"scs">>) -> {<<"application">>, <<"scvp-cv-response">>, []};
all_ext(<<"scurl">>) -> {<<"text">>, <<"vnd.curl.scurl">>, []};
all_ext(<<"sda">>) -> {<<"application">>, <<"vnd.stardivision.draw">>, []};
all_ext(<<"sdc">>) -> {<<"application">>, <<"vnd.stardivision.calc">>, []};
all_ext(<<"sdd">>) -> {<<"application">>, <<"vnd.stardivision.impress">>, []};
all_ext(<<"sdkd">>) -> {<<"application">>, <<"vnd.solent.sdkm+xml">>, []};
all_ext(<<"sdkm">>) -> {<<"application">>, <<"vnd.solent.sdkm+xml">>, []};
all_ext(<<"sdp">>) -> {<<"application">>, <<"sdp">>, []};
all_ext(<<"sdw">>) -> {<<"application">>, <<"vnd.stardivision.writer">>, []};
all_ext(<<"see">>) -> {<<"application">>, <<"vnd.seemail">>, []};
all_ext(<<"seed">>) -> {<<"application">>, <<"vnd.fdsn.seed">>, []};
all_ext(<<"sema">>) -> {<<"application">>, <<"vnd.sema">>, []};
all_ext(<<"semd">>) -> {<<"application">>, <<"vnd.semd">>, []};
all_ext(<<"semf">>) -> {<<"application">>, <<"vnd.semf">>, []};
all_ext(<<"ser">>) -> {<<"application">>, <<"java-serialized-object">>, []};
all_ext(<<"setpay">>) -> {<<"application">>, <<"set-payment-initiation">>, []};
all_ext(<<"setreg">>) -> {<<"application">>, <<"set-registration-initiation">>, []};
all_ext(<<"sfd-hdstx">>) -> {<<"application">>, <<"vnd.hydrostatix.sof-data">>, []};
all_ext(<<"sfs">>) -> {<<"application">>, <<"vnd.spotfire.sfs">>, []};
all_ext(<<"sfv">>) -> {<<"text">>, <<"x-sfv">>, []};
all_ext(<<"sgi">>) -> {<<"image">>, <<"sgi">>, []};
all_ext(<<"sgl">>) -> {<<"application">>, <<"vnd.stardivision.writer-global">>, []};
all_ext(<<"sgml">>) -> {<<"text">>, <<"sgml">>, []};
all_ext(<<"sgm">>) -> {<<"text">>, <<"sgml">>, []};
all_ext(<<"sh">>) -> {<<"application">>, <<"x-sh">>, []};
all_ext(<<"shar">>) -> {<<"application">>, <<"x-shar">>, []};
all_ext(<<"shf">>) -> {<<"application">>, <<"shf+xml">>, []};
all_ext(<<"sid">>) -> {<<"image">>, <<"x-mrsid-image">>, []};
all_ext(<<"sig">>) -> {<<"application">>, <<"pgp-signature">>, []};
all_ext(<<"sil">>) -> {<<"audio">>, <<"silk">>, []};
all_ext(<<"silo">>) -> {<<"model">>, <<"mesh">>, []};
all_ext(<<"sis">>) -> {<<"application">>, <<"vnd.symbian.install">>, []};
all_ext(<<"sisx">>) -> {<<"application">>, <<"vnd.symbian.install">>, []};
all_ext(<<"sit">>) -> {<<"application">>, <<"x-stuffit">>, []};
all_ext(<<"sitx">>) -> {<<"application">>, <<"x-stuffitx">>, []};
all_ext(<<"skd">>) -> {<<"application">>, <<"vnd.koan">>, []};
all_ext(<<"skm">>) -> {<<"application">>, <<"vnd.koan">>, []};
all_ext(<<"skp">>) -> {<<"application">>, <<"vnd.koan">>, []};
all_ext(<<"skt">>) -> {<<"application">>, <<"vnd.koan">>, []};
all_ext(<<"sldm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.slide.macroenabled.12">>, []};
all_ext(<<"sldx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.slide">>, []};
all_ext(<<"slt">>) -> {<<"application">>, <<"vnd.epson.salt">>, []};
all_ext(<<"sm">>) -> {<<"application">>, <<"vnd.stepmania.stepchart">>, []};
all_ext(<<"smf">>) -> {<<"application">>, <<"vnd.stardivision.math">>, []};
all_ext(<<"smi">>) -> {<<"application">>, <<"smil+xml">>, []};
all_ext(<<"smil">>) -> {<<"application">>, <<"smil+xml">>, []};
all_ext(<<"smv">>) -> {<<"video">>, <<"x-smv">>, []};
all_ext(<<"smzip">>) -> {<<"application">>, <<"vnd.stepmania.package">>, []};
all_ext(<<"snd">>) -> {<<"audio">>, <<"basic">>, []};
all_ext(<<"snf">>) -> {<<"application">>, <<"x-font-snf">>, []};
all_ext(<<"so">>) -> {<<"application">>, <<"octet-stream">>, []};
all_ext(<<"spc">>) -> {<<"application">>, <<"x-pkcs7-certificates">>, []};
all_ext(<<"spf">>) -> {<<"application">>, <<"vnd.yamaha.smaf-phrase">>, []};
all_ext(<<"spl">>) -> {<<"application">>, <<"x-futuresplash">>, []};
all_ext(<<"spot">>) -> {<<"text">>, <<"vnd.in3d.spot">>, []};
all_ext(<<"spp">>) -> {<<"application">>, <<"scvp-vp-response">>, []};
all_ext(<<"spq">>) -> {<<"application">>, <<"scvp-vp-request">>, []};
all_ext(<<"spx">>) -> {<<"audio">>, <<"ogg">>, []};
all_ext(<<"sql">>) -> {<<"application">>, <<"x-sql">>, []};
all_ext(<<"src">>) -> {<<"application">>, <<"x-wais-source">>, []};
all_ext(<<"srt">>) -> {<<"application">>, <<"x-subrip">>, []};
all_ext(<<"sru">>) -> {<<"application">>, <<"sru+xml">>, []};
all_ext(<<"srx">>) -> {<<"application">>, <<"sparql-results+xml">>, []};
all_ext(<<"ssdl">>) -> {<<"application">>, <<"ssdl+xml">>, []};
all_ext(<<"sse">>) -> {<<"application">>, <<"vnd.kodak-descriptor">>, []};
all_ext(<<"ssf">>) -> {<<"application">>, <<"vnd.epson.ssf">>, []};
all_ext(<<"ssml">>) -> {<<"application">>, <<"ssml+xml">>, []};
all_ext(<<"st">>) -> {<<"application">>, <<"vnd.sailingtracker.track">>, []};
all_ext(<<"stc">>) -> {<<"application">>, <<"vnd.sun.xml.calc.template">>, []};
all_ext(<<"std">>) -> {<<"application">>, <<"vnd.sun.xml.draw.template">>, []};
all_ext(<<"s">>) -> {<<"text">>, <<"x-asm">>, []};
all_ext(<<"stf">>) -> {<<"application">>, <<"vnd.wt.stf">>, []};
all_ext(<<"sti">>) -> {<<"application">>, <<"vnd.sun.xml.impress.template">>, []};
all_ext(<<"stk">>) -> {<<"application">>, <<"hyperstudio">>, []};
all_ext(<<"stl">>) -> {<<"application">>, <<"vnd.ms-pki.stl">>, []};
all_ext(<<"str">>) -> {<<"application">>, <<"vnd.pg.format">>, []};
all_ext(<<"stw">>) -> {<<"application">>, <<"vnd.sun.xml.writer.template">>, []};
all_ext(<<"sub">>) -> {<<"image">>, <<"vnd.dvb.subtitle">>, []};
all_ext(<<"sus">>) -> {<<"application">>, <<"vnd.sus-calendar">>, []};
all_ext(<<"susp">>) -> {<<"application">>, <<"vnd.sus-calendar">>, []};
all_ext(<<"sv4cpio">>) -> {<<"application">>, <<"x-sv4cpio">>, []};
all_ext(<<"sv4crc">>) -> {<<"application">>, <<"x-sv4crc">>, []};
all_ext(<<"svc">>) -> {<<"application">>, <<"vnd.dvb.service">>, []};
all_ext(<<"svd">>) -> {<<"application">>, <<"vnd.svd">>, []};
all_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
all_ext(<<"svgz">>) -> {<<"image">>, <<"svg+xml">>, []};
all_ext(<<"swa">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"swf">>) -> {<<"application">>, <<"x-shockwave-flash">>, []};
all_ext(<<"swi">>) -> {<<"application">>, <<"vnd.aristanetworks.swi">>, []};
all_ext(<<"sxc">>) -> {<<"application">>, <<"vnd.sun.xml.calc">>, []};
all_ext(<<"sxd">>) -> {<<"application">>, <<"vnd.sun.xml.draw">>, []};
all_ext(<<"sxg">>) -> {<<"application">>, <<"vnd.sun.xml.writer.global">>, []};
all_ext(<<"sxi">>) -> {<<"application">>, <<"vnd.sun.xml.impress">>, []};
all_ext(<<"sxm">>) -> {<<"application">>, <<"vnd.sun.xml.math">>, []};
all_ext(<<"sxw">>) -> {<<"application">>, <<"vnd.sun.xml.writer">>, []};
all_ext(<<"t3">>) -> {<<"application">>, <<"x-t3vm-image">>, []};
all_ext(<<"taglet">>) -> {<<"application">>, <<"vnd.mynfc">>, []};
all_ext(<<"tao">>) -> {<<"application">>, <<"vnd.tao.intent-module-archive">>, []};
all_ext(<<"tar">>) -> {<<"application">>, <<"x-tar">>, []};
all_ext(<<"tcap">>) -> {<<"application">>, <<"vnd.3gpp2.tcap">>, []};
all_ext(<<"tcl">>) -> {<<"application">>, <<"x-tcl">>, []};
all_ext(<<"teacher">>) -> {<<"application">>, <<"vnd.smart.teacher">>, []};
all_ext(<<"tei">>) -> {<<"application">>, <<"tei+xml">>, []};
all_ext(<<"teicorpus">>) -> {<<"application">>, <<"tei+xml">>, []};
all_ext(<<"tex">>) -> {<<"application">>, <<"x-tex">>, []};
all_ext(<<"texi">>) -> {<<"application">>, <<"x-texinfo">>, []};
all_ext(<<"texinfo">>) -> {<<"application">>, <<"x-texinfo">>, []};
all_ext(<<"text">>) -> {<<"text">>, <<"plain">>, []};
all_ext(<<"tfi">>) -> {<<"application">>, <<"thraud+xml">>, []};
all_ext(<<"tfm">>) -> {<<"application">>, <<"x-tex-tfm">>, []};
all_ext(<<"tga">>) -> {<<"image">>, <<"x-tga">>, []};
all_ext(<<"thmx">>) -> {<<"application">>, <<"vnd.ms-officetheme">>, []};
all_ext(<<"tiff">>) -> {<<"image">>, <<"tiff">>, []};
all_ext(<<"tif">>) -> {<<"image">>, <<"tiff">>, []};
all_ext(<<"tmo">>) -> {<<"application">>, <<"vnd.tmobile-livetv">>, []};
all_ext(<<"torrent">>) -> {<<"application">>, <<"x-bittorrent">>, []};
all_ext(<<"tpl">>) -> {<<"application">>, <<"vnd.groove-tool-template">>, []};
all_ext(<<"tpt">>) -> {<<"application">>, <<"vnd.trid.tpt">>, []};
all_ext(<<"tra">>) -> {<<"application">>, <<"vnd.trueapp">>, []};
all_ext(<<"trm">>) -> {<<"application">>, <<"x-msterminal">>, []};
all_ext(<<"tr">>) -> {<<"text">>, <<"troff">>, []};
all_ext(<<"tsd">>) -> {<<"application">>, <<"timestamped-data">>, []};
all_ext(<<"tsv">>) -> {<<"text">>, <<"tab-separated-values">>, []};
all_ext(<<"ttc">>) -> {<<"application">>, <<"x-font-ttf">>, []};
all_ext(<<"t">>) -> {<<"text">>, <<"troff">>, []};
all_ext(<<"ttf">>) -> {<<"application">>, <<"x-font-ttf">>, []};
all_ext(<<"ttl">>) -> {<<"text">>, <<"turtle">>, []};
all_ext(<<"twd">>) -> {<<"application">>, <<"vnd.simtech-mindmapper">>, []};
all_ext(<<"twds">>) -> {<<"application">>, <<"vnd.simtech-mindmapper">>, []};
all_ext(<<"txd">>) -> {<<"application">>, <<"vnd.genomatix.tuxedo">>, []};
all_ext(<<"txf">>) -> {<<"application">>, <<"vnd.mobius.txf">>, []};
all_ext(<<"txt">>) -> {<<"text">>, <<"plain">>, []};
all_ext(<<"u32">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
all_ext(<<"udeb">>) -> {<<"application">>, <<"x-debian-package">>, []};
all_ext(<<"ufd">>) -> {<<"application">>, <<"vnd.ufdl">>, []};
all_ext(<<"ufdl">>) -> {<<"application">>, <<"vnd.ufdl">>, []};
all_ext(<<"ulx">>) -> {<<"application">>, <<"x-glulx">>, []};
all_ext(<<"umj">>) -> {<<"application">>, <<"vnd.umajin">>, []};
all_ext(<<"unityweb">>) -> {<<"application">>, <<"vnd.unity">>, []};
all_ext(<<"uoml">>) -> {<<"application">>, <<"vnd.uoml+xml">>, []};
all_ext(<<"uris">>) -> {<<"text">>, <<"uri-list">>, []};
all_ext(<<"uri">>) -> {<<"text">>, <<"uri-list">>, []};
all_ext(<<"urls">>) -> {<<"text">>, <<"uri-list">>, []};
all_ext(<<"ustar">>) -> {<<"application">>, <<"x-ustar">>, []};
all_ext(<<"utz">>) -> {<<"application">>, <<"vnd.uiq.theme">>, []};
all_ext(<<"uu">>) -> {<<"text">>, <<"x-uuencode">>, []};
all_ext(<<"uva">>) -> {<<"audio">>, <<"vnd.dece.audio">>, []};
all_ext(<<"uvd">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
all_ext(<<"uvf">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
all_ext(<<"uvg">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
all_ext(<<"uvh">>) -> {<<"video">>, <<"vnd.dece.hd">>, []};
all_ext(<<"uvi">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
all_ext(<<"uvm">>) -> {<<"video">>, <<"vnd.dece.mobile">>, []};
all_ext(<<"uvp">>) -> {<<"video">>, <<"vnd.dece.pd">>, []};
all_ext(<<"uvs">>) -> {<<"video">>, <<"vnd.dece.sd">>, []};
all_ext(<<"uvt">>) -> {<<"application">>, <<"vnd.dece.ttml+xml">>, []};
all_ext(<<"uvu">>) -> {<<"video">>, <<"vnd.uvvu.mp4">>, []};
all_ext(<<"uvva">>) -> {<<"audio">>, <<"vnd.dece.audio">>, []};
all_ext(<<"uvvd">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
all_ext(<<"uvvf">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
all_ext(<<"uvvg">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
all_ext(<<"uvvh">>) -> {<<"video">>, <<"vnd.dece.hd">>, []};
all_ext(<<"uvvi">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
all_ext(<<"uvvm">>) -> {<<"video">>, <<"vnd.dece.mobile">>, []};
all_ext(<<"uvvp">>) -> {<<"video">>, <<"vnd.dece.pd">>, []};
all_ext(<<"uvvs">>) -> {<<"video">>, <<"vnd.dece.sd">>, []};
all_ext(<<"uvvt">>) -> {<<"application">>, <<"vnd.dece.ttml+xml">>, []};
all_ext(<<"uvvu">>) -> {<<"video">>, <<"vnd.uvvu.mp4">>, []};
all_ext(<<"uvv">>) -> {<<"video">>, <<"vnd.dece.video">>, []};
all_ext(<<"uvvv">>) -> {<<"video">>, <<"vnd.dece.video">>, []};
all_ext(<<"uvvx">>) -> {<<"application">>, <<"vnd.dece.unspecified">>, []};
all_ext(<<"uvvz">>) -> {<<"application">>, <<"vnd.dece.zip">>, []};
all_ext(<<"uvx">>) -> {<<"application">>, <<"vnd.dece.unspecified">>, []};
all_ext(<<"uvz">>) -> {<<"application">>, <<"vnd.dece.zip">>, []};
all_ext(<<"vcard">>) -> {<<"text">>, <<"vcard">>, []};
all_ext(<<"vcd">>) -> {<<"application">>, <<"x-cdlink">>, []};
all_ext(<<"vcf">>) -> {<<"text">>, <<"x-vcard">>, []};
all_ext(<<"vcg">>) -> {<<"application">>, <<"vnd.groove-vcard">>, []};
all_ext(<<"vcs">>) -> {<<"text">>, <<"x-vcalendar">>, []};
all_ext(<<"vcx">>) -> {<<"application">>, <<"vnd.vcx">>, []};
all_ext(<<"vis">>) -> {<<"application">>, <<"vnd.visionary">>, []};
all_ext(<<"viv">>) -> {<<"video">>, <<"vnd.vivo">>, []};
all_ext(<<"vob">>) -> {<<"video">>, <<"x-ms-vob">>, []};
all_ext(<<"vor">>) -> {<<"application">>, <<"vnd.stardivision.writer">>, []};
all_ext(<<"vox">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
all_ext(<<"vrml">>) -> {<<"model">>, <<"vrml">>, []};
all_ext(<<"vsd">>) -> {<<"application">>, <<"vnd.visio">>, []};
all_ext(<<"vsf">>) -> {<<"application">>, <<"vnd.vsf">>, []};
all_ext(<<"vss">>) -> {<<"application">>, <<"vnd.visio">>, []};
all_ext(<<"vst">>) -> {<<"application">>, <<"vnd.visio">>, []};
all_ext(<<"vsw">>) -> {<<"application">>, <<"vnd.visio">>, []};
all_ext(<<"vtu">>) -> {<<"model">>, <<"vnd.vtu">>, []};
all_ext(<<"vxml">>) -> {<<"application">>, <<"voicexml+xml">>, []};
all_ext(<<"w3d">>) -> {<<"application">>, <<"x-director">>, []};
all_ext(<<"wad">>) -> {<<"application">>, <<"x-doom">>, []};
all_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
all_ext(<<"wax">>) -> {<<"audio">>, <<"x-ms-wax">>, []};
all_ext(<<"wbmp">>) -> {<<"image">>, <<"vnd.wap.wbmp">>, []};
all_ext(<<"wbs">>) -> {<<"application">>, <<"vnd.criticaltools.wbs+xml">>, []};
all_ext(<<"wbxml">>) -> {<<"application">>, <<"vnd.wap.wbxml">>, []};
all_ext(<<"wcm">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
all_ext(<<"wdb">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
all_ext(<<"wdp">>) -> {<<"image">>, <<"vnd.ms-photo">>, []};
all_ext(<<"weba">>) -> {<<"audio">>, <<"webm">>, []};
all_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
all_ext(<<"webp">>) -> {<<"image">>, <<"webp">>, []};
all_ext(<<"wg">>) -> {<<"application">>, <<"vnd.pmi.widget">>, []};
all_ext(<<"wgt">>) -> {<<"application">>, <<"widget">>, []};
all_ext(<<"wks">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
all_ext(<<"wma">>) -> {<<"audio">>, <<"x-ms-wma">>, []};
all_ext(<<"wmd">>) -> {<<"application">>, <<"x-ms-wmd">>, []};
all_ext(<<"wmf">>) -> {<<"application">>, <<"x-msmetafile">>, []};
all_ext(<<"wmlc">>) -> {<<"application">>, <<"vnd.wap.wmlc">>, []};
all_ext(<<"wmlsc">>) -> {<<"application">>, <<"vnd.wap.wmlscriptc">>, []};
all_ext(<<"wmls">>) -> {<<"text">>, <<"vnd.wap.wmlscript">>, []};
all_ext(<<"wml">>) -> {<<"text">>, <<"vnd.wap.wml">>, []};
all_ext(<<"wm">>) -> {<<"video">>, <<"x-ms-wm">>, []};
all_ext(<<"wmv">>) -> {<<"video">>, <<"x-ms-wmv">>, []};
all_ext(<<"wmx">>) -> {<<"video">>, <<"x-ms-wmx">>, []};
all_ext(<<"wmz">>) -> {<<"application">>, <<"x-msmetafile">>, []};
all_ext(<<"woff">>) -> {<<"application">>, <<"font-woff">>, []};
all_ext(<<"wpd">>) -> {<<"application">>, <<"vnd.wordperfect">>, []};
all_ext(<<"wpl">>) -> {<<"application">>, <<"vnd.ms-wpl">>, []};
all_ext(<<"wps">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
all_ext(<<"wqd">>) -> {<<"application">>, <<"vnd.wqd">>, []};
all_ext(<<"wri">>) -> {<<"application">>, <<"x-mswrite">>, []};
all_ext(<<"wrl">>) -> {<<"model">>, <<"vrml">>, []};
all_ext(<<"wsdl">>) -> {<<"application">>, <<"wsdl+xml">>, []};
all_ext(<<"wspolicy">>) -> {<<"application">>, <<"wspolicy+xml">>, []};
all_ext(<<"wtb">>) -> {<<"application">>, <<"vnd.webturbo">>, []};
all_ext(<<"wvx">>) -> {<<"video">>, <<"x-ms-wvx">>, []};
all_ext(<<"x32">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
all_ext(<<"x3db">>) -> {<<"model">>, <<"x3d+binary">>, []};
all_ext(<<"x3dbz">>) -> {<<"model">>, <<"x3d+binary">>, []};
all_ext(<<"x3d">>) -> {<<"model">>, <<"x3d+xml">>, []};
all_ext(<<"x3dv">>) -> {<<"model">>, <<"x3d+vrml">>, []};
all_ext(<<"x3dvz">>) -> {<<"model">>, <<"x3d+vrml">>, []};
all_ext(<<"x3dz">>) -> {<<"model">>, <<"x3d+xml">>, []};
all_ext(<<"xaml">>) -> {<<"application">>, <<"xaml+xml">>, []};
all_ext(<<"xap">>) -> {<<"application">>, <<"x-silverlight-app">>, []};
all_ext(<<"xar">>) -> {<<"application">>, <<"vnd.xara">>, []};
all_ext(<<"xbap">>) -> {<<"application">>, <<"x-ms-xbap">>, []};
all_ext(<<"xbd">>) -> {<<"application">>, <<"vnd.fujixerox.docuworks.binder">>, []};
all_ext(<<"xbm">>) -> {<<"image">>, <<"x-xbitmap">>, []};
all_ext(<<"xdf">>) -> {<<"application">>, <<"xcap-diff+xml">>, []};
all_ext(<<"xdm">>) -> {<<"application">>, <<"vnd.syncml.dm+xml">>, []};
all_ext(<<"xdp">>) -> {<<"application">>, <<"vnd.adobe.xdp+xml">>, []};
all_ext(<<"xdssc">>) -> {<<"application">>, <<"dssc+xml">>, []};
all_ext(<<"xdw">>) -> {<<"application">>, <<"vnd.fujixerox.docuworks">>, []};
all_ext(<<"xenc">>) -> {<<"application">>, <<"xenc+xml">>, []};
all_ext(<<"xer">>) -> {<<"application">>, <<"patch-ops-error+xml">>, []};
all_ext(<<"xfdf">>) -> {<<"application">>, <<"vnd.adobe.xfdf">>, []};
all_ext(<<"xfdl">>) -> {<<"application">>, <<"vnd.xfdl">>, []};
all_ext(<<"xht">>) -> {<<"application">>, <<"xhtml+xml">>, []};
all_ext(<<"xhtml">>) -> {<<"application">>, <<"xhtml+xml">>, []};
all_ext(<<"xhvml">>) -> {<<"application">>, <<"xv+xml">>, []};
all_ext(<<"xif">>) -> {<<"image">>, <<"vnd.xiff">>, []};
all_ext(<<"xla">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
all_ext(<<"xlam">>) -> {<<"application">>, <<"vnd.ms-excel.addin.macroenabled.12">>, []};
all_ext(<<"xlc">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
all_ext(<<"xlf">>) -> {<<"application">>, <<"x-xliff+xml">>, []};
all_ext(<<"xlm">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
all_ext(<<"xls">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
all_ext(<<"xlsb">>) -> {<<"application">>, <<"vnd.ms-excel.sheet.binary.macroenabled.12">>, []};
all_ext(<<"xlsm">>) -> {<<"application">>, <<"vnd.ms-excel.sheet.macroenabled.12">>, []};
all_ext(<<"xlsx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.spreadsheetml.sheet">>, []};
all_ext(<<"xlt">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
all_ext(<<"xltm">>) -> {<<"application">>, <<"vnd.ms-excel.template.macroenabled.12">>, []};
all_ext(<<"xltx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.spreadsheetml.template">>, []};
all_ext(<<"xlw">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
all_ext(<<"xm">>) -> {<<"audio">>, <<"xm">>, []};
all_ext(<<"xml">>) -> {<<"application">>, <<"xml">>, []};
all_ext(<<"xo">>) -> {<<"application">>, <<"vnd.olpc-sugar">>, []};
all_ext(<<"xop">>) -> {<<"application">>, <<"xop+xml">>, []};
all_ext(<<"xpi">>) -> {<<"application">>, <<"x-xpinstall">>, []};
all_ext(<<"xpl">>) -> {<<"application">>, <<"xproc+xml">>, []};
all_ext(<<"xpm">>) -> {<<"image">>, <<"x-xpixmap">>, []};
all_ext(<<"xpr">>) -> {<<"application">>, <<"vnd.is-xpr">>, []};
all_ext(<<"xps">>) -> {<<"application">>, <<"vnd.ms-xpsdocument">>, []};
all_ext(<<"xpw">>) -> {<<"application">>, <<"vnd.intercon.formnet">>, []};
all_ext(<<"xpx">>) -> {<<"application">>, <<"vnd.intercon.formnet">>, []};
all_ext(<<"xsl">>) -> {<<"application">>, <<"xml">>, []};
all_ext(<<"xslt">>) -> {<<"application">>, <<"xslt+xml">>, []};
all_ext(<<"xsm">>) -> {<<"application">>, <<"vnd.syncml+xml">>, []};
all_ext(<<"xspf">>) -> {<<"application">>, <<"xspf+xml">>, []};
all_ext(<<"xul">>) -> {<<"application">>, <<"vnd.mozilla.xul+xml">>, []};
all_ext(<<"xvm">>) -> {<<"application">>, <<"xv+xml">>, []};
all_ext(<<"xvml">>) -> {<<"application">>, <<"xv+xml">>, []};
all_ext(<<"xwd">>) -> {<<"image">>, <<"x-xwindowdump">>, []};
all_ext(<<"xyz">>) -> {<<"chemical">>, <<"x-xyz">>, []};
all_ext(<<"xz">>) -> {<<"application">>, <<"x-xz">>, []};
all_ext(<<"yang">>) -> {<<"application">>, <<"yang">>, []};
all_ext(<<"yin">>) -> {<<"application">>, <<"yin+xml">>, []};
all_ext(<<"z1">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"z2">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"z3">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"z4">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"z5">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"z6">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"z7">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"z8">>) -> {<<"application">>, <<"x-zmachine">>, []};
all_ext(<<"zaz">>) -> {<<"application">>, <<"vnd.zzazz.deck+xml">>, []};
all_ext(<<"zip">>) -> {<<"application">>, <<"zip">>, []};
all_ext(<<"zir">>) -> {<<"application">>, <<"vnd.zul">>, []};
all_ext(<<"zirz">>) -> {<<"application">>, <<"vnd.zul">>, []};
all_ext(<<"zmm">>) -> {<<"application">>, <<"vnd.handheld-entertainment+xml">>, []};
%% GENERATED
all_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
web_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
web_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
web_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
web_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
web_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
web_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
web_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
web_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
web_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
web_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
web_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
web_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
web_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
web_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
web_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
web_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
web_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_mimetypes).
-export([all/1]).
-export([web/1]).
%% @doc Return the mimetype for any file by looking at its extension.
-spec all(file:filename_all()) -> {binary(), binary(), []}.
all(Path) ->
case filename:extension(Path) of
<<>> -> {<<"application">>, <<"octet-stream">>, []};
<< $., Ext/binary >> -> all_ext(Ext)
end.
%% @doc Return the mimetype for a Web related file by looking at its extension.
-spec web(file:filename_all()) -> {binary(), binary(), []}.
web(Path) ->
case filename:extension(Path) of
<<>> -> {<<"application">>, <<"octet-stream">>, []};
<< $., Ext/binary >> -> web_ext(Ext)
end.
%% Internal.
%% GENERATED
all_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
web_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
web_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
web_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
web_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
web_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
web_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
web_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
web_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
web_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
web_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
web_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
web_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
web_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
web_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
web_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
web_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
web_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 |
%% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_multipart).
%% Parsing.
-export([parse_headers/2]).
-export([parse_body/2]).
%% Building.
-export([boundary/0]).
-export([first_part/2]).
-export([part/2]).
-export([close/1]).
%% Headers.
-export([form_data/1]).
-export([parse_content_disposition/1]).
-export([parse_content_transfer_encoding/1]).
-export([parse_content_type/1]).
-type headers() :: [{iodata(), iodata()}].
-export_type([headers/0]).
-include("cow_inline.hrl").
-define(TEST1_MIME, <<
"This is a message with multiple parts in MIME format.\r\n"
"--frontier\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"This is the body of the message.\r\n"
"--frontier\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Transfer-Encoding: base64\r\n"
"\r\n"
"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
"Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==\r\n"
"--frontier--"
>>).
-define(TEST1_BOUNDARY, <<"frontier">>).
-define(TEST2_MIME, <<
"--AaB03x\r\n"
"Content-Disposition: form-data; name=\"submit-name\"\r\n"
"\r\n"
"Larry\r\n"
"--AaB03x\r\n"
"Content-Disposition: form-data; name=\"files\"\r\n"
"Content-Type: multipart/mixed; boundary=BbC04y\r\n"
"\r\n"
"--BbC04y\r\n"
"Content-Disposition: file; filename=\"file1.txt\"\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"... contents of file1.txt ...\r\n"
"--BbC04y\r\n"
"Content-Disposition: file; filename=\"file2.gif\"\r\n"
"Content-Type: image/gif\r\n"
"Content-Transfer-Encoding: binary\r\n"
"\r\n"
"...contents of file2.gif...\r\n"
"--BbC04y--\r\n"
"--AaB03x--"
>>).
-define(TEST2_BOUNDARY, <<"AaB03x">>).
-define(TEST3_MIME, <<
"This is the preamble.\r\n"
"--boundary\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"This is the body of the message.\r\n"
"--boundary--"
"\r\nThis is the epilogue. Here it includes leading CRLF"
>>).
-define(TEST3_BOUNDARY, <<"boundary">>).
-define(TEST4_MIME, <<
"This is the preamble.\r\n"
"--boundary\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"This is the body of the message.\r\n"
"--boundary--"
"\r\n"
>>).
-define(TEST4_BOUNDARY, <<"boundary">>).
%% Parsing.
%%
%% The multipart format is defined in RFC 2045.
%% @doc Parse the headers for the next multipart part.
%%
%% This function skips any preamble before the boundary.
%% The preamble may be retrieved using parse_body/2.
%%
%% This function will accept input of any size, it is
%% up to the caller to limit it if needed.
-spec parse_headers(binary(), binary())
-> more | {more, binary()}
| {ok, headers(), binary()}
| {done, binary()}.
%% If the stream starts with the boundary we can make a few assumptions
%% and quickly figure out if we got the complete list of headers.
parse_headers(<< "--", Stream/bits >>, Boundary) ->
BoundarySize = byte_size(Boundary),
case Stream of
%% Last boundary. Return the epilogue.
<< Boundary:BoundarySize/binary, "--", Stream2/bits >> ->
{done, Stream2};
<< Boundary:BoundarySize/binary, Stream2/bits >> ->
%% We have all the headers only if there is a \r\n\r\n
%% somewhere in the data after the boundary.
case binary:match(Stream2, <<"\r\n\r\n">>) of
nomatch ->
more;
_ ->
before_parse_headers(Stream2)
end;
%% If there isn't enough to represent Boundary \r\n\r\n
%% then we definitely don't have all the headers.
_ when byte_size(Stream) < byte_size(Boundary) + 4 ->
more;
%% Otherwise we have preamble data to skip.
%% We still got rid of the first two misleading bytes.
_ ->
skip_preamble(Stream, Boundary)
end;
%% Otherwise we have preamble data to skip.
parse_headers(Stream, Boundary) ->
skip_preamble(Stream, Boundary).
%% We need to find the boundary and a \r\n\r\n after that.
%% Since the boundary isn't at the start, it must be right
%% after a \r\n too.
skip_preamble(Stream, Boundary) ->
case binary:match(Stream, <<"\r\n--", Boundary/bits >>) of
%% No boundary, need more data.
nomatch ->
%% We can safely skip the size of the stream
%% minus the last 3 bytes which may be a partial boundary.
SkipSize = byte_size(Stream) - 3,
case SkipSize > 0 of
false ->
more;
true ->
<< _:SkipSize/binary, Stream2/bits >> = Stream,
{more, Stream2}
end;
{Start, Length} ->
Start2 = Start + Length,
<< _:Start2/binary, Stream2/bits >> = Stream,
case Stream2 of
%% Last boundary. Return the epilogue.
<< "--", Stream3/bits >> ->
{done, Stream3};
_ ->
case binary:match(Stream, <<"\r\n\r\n">>) of
%% We don't have the full headers.
nomatch ->
{more, Stream2};
_ ->
before_parse_headers(Stream2)
end
end
end.
%% There is a line break right after the boundary, skip it.
%%
%% We only skip it now because there might be no headers at all,
%% which means the \r\n\r\n indicating the end of headers also
%% includes this line break.
before_parse_headers(<< "\r\n", Stream/bits >>) ->
parse_hd_name(Stream, [], <<>>).
parse_hd_name(<< C, Rest/bits >>, H, SoFar) ->
case C of
$: -> parse_hd_before_value(Rest, H, SoFar);
$\s -> parse_hd_name_ws(Rest, H, SoFar);
$\t -> parse_hd_name_ws(Rest, H, SoFar);
?INLINE_LOWERCASE(parse_hd_name, Rest, H, SoFar)
end.
parse_hd_name_ws(<< C, Rest/bits >>, H, Name) ->
case C of
$\s -> parse_hd_name_ws(Rest, H, Name);
$\t -> parse_hd_name_ws(Rest, H, Name);
$: -> parse_hd_before_value(Rest, H, Name)
end.
parse_hd_before_value(<< $\s, Rest/bits >>, H, N) ->
parse_hd_before_value(Rest, H, N);
parse_hd_before_value(<< $\t, Rest/bits >>, H, N) ->
parse_hd_before_value(Rest, H, N);
parse_hd_before_value(Buffer, H, N) ->
parse_hd_value(Buffer, H, N, <<>>).
parse_hd_value(<< $\r, Rest/bits >>, Headers, Name, SoFar) ->
case Rest of
<< "\n\r\n", Rest2/bits >> ->
{ok, [{Name, SoFar}|Headers], Rest2};
<< $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
parse_hd_value(Rest2, Headers, Name, SoFar);
<< $\n, Rest2/bits >> ->
parse_hd_name(Rest2, [{Name, SoFar}|Headers], <<>>)
end;
parse_hd_value(<< C, Rest/bits >>, H, N, SoFar) ->
parse_hd_value(Rest, H, N, << SoFar/binary, C >>).
%% @doc Parse the body of the current multipart part.
%%
%% The body is everything until the next boundary.
-spec parse_body(binary(), binary())
-> {ok, binary()} | {ok, binary(), binary()}
| done | {done, binary()} | {done, binary(), binary()}.
parse_body(Stream, Boundary) ->
BoundarySize = byte_size(Boundary),
case Stream of
<< "--", Boundary:BoundarySize/binary, _/bits >> ->
done;
_ ->
case binary:match(Stream, << "\r\n--", Boundary/bits >>) of
%% No boundary, check for a possible partial at the end.
%% Return more or less of the body depending on the result.
nomatch ->
StreamSize = byte_size(Stream),
From = StreamSize - BoundarySize - 3,
MatchOpts = if
%% Binary too small to contain boundary, check it fully.
From < 0 -> [];
%% Optimize, only check the end of the binary.
true -> [{scope, {From, StreamSize - From}}]
end,
case binary:match(Stream, <<"\r">>, MatchOpts) of
nomatch ->
{ok, Stream};
{Pos, _} ->
case Stream of
<< Body:Pos/binary >> ->
{ok, Body};
<< Body:Pos/binary, Rest/bits >> ->
{ok, Body, Rest}
end
end;
%% Boundary found, this is the last chunk of the body.
{Pos, _} ->
case Stream of
<< Body:Pos/binary, "\r\n" >> ->
{done, Body};
<< Body:Pos/binary, "\r\n", Rest/bits >> ->
{done, Body, Rest};
<< Body:Pos/binary, Rest/bits >> ->
{done, Body, Rest}
end
end
end.
-ifdef(TEST).
parse_test() ->
H1 = [{<<"content-type">>, <<"text/plain">>}],
Body1 = <<"This is the body of the message.">>,
H2 = lists:sort([{<<"content-type">>, <<"application/octet-stream">>},
{<<"content-transfer-encoding">>, <<"base64">>}]),
Body2 = <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
"Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
{ok, H1, Rest} = parse_headers(?TEST1_MIME, ?TEST1_BOUNDARY),
{done, Body1, Rest2} = parse_body(Rest, ?TEST1_BOUNDARY),
done = parse_body(Rest2, ?TEST1_BOUNDARY),
{ok, H2Unsorted, Rest3} = parse_headers(Rest2, ?TEST1_BOUNDARY),
H2 = lists:sort(H2Unsorted),
{done, Body2, Rest4} = parse_body(Rest3, ?TEST1_BOUNDARY),
done = parse_body(Rest4, ?TEST1_BOUNDARY),
{done, <<>>} = parse_headers(Rest4, ?TEST1_BOUNDARY),
ok.
parse_interleaved_test() ->
H1 = [{<<"content-disposition">>, <<"form-data; name=\"submit-name\"">>}],
Body1 = <<"Larry">>,
H2 = lists:sort([{<<"content-disposition">>, <<"form-data; name=\"files\"">>},
{<<"content-type">>, <<"multipart/mixed; boundary=BbC04y">>}]),
InH1 = lists:sort([{<<"content-disposition">>, <<"file; filename=\"file1.txt\"">>},
{<<"content-type">>, <<"text/plain">>}]),
InBody1 = <<"... contents of file1.txt ...">>,
InH2 = lists:sort([{<<"content-disposition">>, <<"file; filename=\"file2.gif\"">>},
{<<"content-type">>, <<"image/gif">>},
{<<"content-transfer-encoding">>, <<"binary">>}]),
InBody2 = <<"...contents of file2.gif...">>,
{ok, H1, Rest} = parse_headers(?TEST2_MIME, ?TEST2_BOUNDARY),
{done, Body1, Rest2} = parse_body(Rest, ?TEST2_BOUNDARY),
done = parse_body(Rest2, ?TEST2_BOUNDARY),
{ok, H2Unsorted, Rest3} = parse_headers(Rest2, ?TEST2_BOUNDARY),
H2 = lists:sort(H2Unsorted),
{_, ContentType} = lists:keyfind(<<"content-type">>, 1, H2),
{<<"multipart">>, <<"mixed">>, [{<<"boundary">>, InBoundary}]}
= parse_content_type(ContentType),
{ok, InH1Unsorted, InRest} = parse_headers(Rest3, InBoundary),
InH1 = lists:sort(InH1Unsorted),
{done, InBody1, InRest2} = parse_body(InRest, InBoundary),
done = parse_body(InRest2, InBoundary),
{ok, InH2Unsorted, InRest3} = parse_headers(InRest2, InBoundary),
InH2 = lists:sort(InH2Unsorted),
{done, InBody2, InRest4} = parse_body(InRest3, InBoundary),
done = parse_body(InRest4, InBoundary),
{done, Rest4} = parse_headers(InRest4, InBoundary),
{done, <<>>} = parse_headers(Rest4, ?TEST2_BOUNDARY),
ok.
parse_epilogue_test() ->
H1 = [{<<"content-type">>, <<"text/plain">>}],
Body1 = <<"This is the body of the message.">>,
Epilogue = <<"\r\nThis is the epilogue. Here it includes leading CRLF">>,
{ok, H1, Rest} = parse_headers(?TEST3_MIME, ?TEST3_BOUNDARY),
{done, Body1, Rest2} = parse_body(Rest, ?TEST3_BOUNDARY),
done = parse_body(Rest2, ?TEST3_BOUNDARY),
{done, Epilogue} = parse_headers(Rest2, ?TEST3_BOUNDARY),
ok.
parse_epilogue_crlf_test() ->
H1 = [{<<"content-type">>, <<"text/plain">>}],
Body1 = <<"This is the body of the message.">>,
Epilogue = <<"\r\n">>,
{ok, H1, Rest} = parse_headers(?TEST4_MIME, ?TEST4_BOUNDARY),
{done, Body1, Rest2} = parse_body(Rest, ?TEST4_BOUNDARY),
done = parse_body(Rest2, ?TEST4_BOUNDARY),
{done, Epilogue} = parse_headers(Rest2, ?TEST4_BOUNDARY),
ok.
parse_partial_test() ->
{ok, <<0:8000, "abcdef">>, <<"\rghij">>}
= parse_body(<<0:8000, "abcdef\rghij">>, <<"boundary">>),
{ok, <<"abcdef">>, <<"\rghij">>}
= parse_body(<<"abcdef\rghij">>, <<"boundary">>),
{ok, <<"abc">>, <<"\rdef">>}
= parse_body(<<"abc\rdef">>, <<"boundaryboundary">>),
{ok, <<0:8000, "abcdef">>, <<"\r\nghij">>}
= parse_body(<<0:8000, "abcdef\r\nghij">>, <<"boundary">>),
{ok, <<"abcdef">>, <<"\r\nghij">>}
= parse_body(<<"abcdef\r\nghij">>, <<"boundary">>),
{ok, <<"abc">>, <<"\r\ndef">>}
= parse_body(<<"abc\r\ndef">>, <<"boundaryboundary">>),
{ok, <<"boundary">>, <<"\r">>}
= parse_body(<<"boundary\r">>, <<"boundary">>),
{ok, <<"boundary">>, <<"\r\n">>}
= parse_body(<<"boundary\r\n">>, <<"boundary">>),
{ok, <<"boundary">>, <<"\r\n-">>}
= parse_body(<<"boundary\r\n-">>, <<"boundary">>),
{ok, <<"boundary">>, <<"\r\n--">>}
= parse_body(<<"boundary\r\n--">>, <<"boundary">>),
ok.
-endif.
-ifdef(PERF).
perf_parse_multipart(Stream, Boundary) ->
case parse_headers(Stream, Boundary) of
{ok, _, Rest} ->
{_, _, Rest2} = parse_body(Rest, Boundary),
perf_parse_multipart(Rest2, Boundary);
{done, _} ->
ok
end.
horse_parse() ->
horse:repeat(50000,
perf_parse_multipart(?TEST1_MIME, ?TEST1_BOUNDARY)
).
-endif.
%% Building.
%% @doc Generate a new random boundary.
%%
%% The boundary generated has a low probability of ever appearing
%% in the data.
-spec boundary() -> binary().
boundary() ->
base64:encode(crypto:rand_bytes(48)).
%% @doc Return the first part's head.
%%
%% This works exactly like the part/2 function except there is
%% no leading \r\n. It's not required to use this function,
%% just makes the output a little smaller and prettier.
-spec first_part(binary(), headers()) -> iodata().
first_part(Boundary, Headers) ->
[<<"--">>, Boundary, <<"\r\n">>, headers_to_iolist(Headers, [])].
%% @doc Return a part's head.
-spec part(binary(), headers()) -> iodata().
part(Boundary, Headers) ->
[<<"\r\n--">>, Boundary, <<"\r\n">>, headers_to_iolist(Headers, [])].
headers_to_iolist([], Acc) ->
lists:reverse([<<"\r\n">>|Acc]);
headers_to_iolist([{N, V}|Tail], Acc) ->
%% We don't want to create a sublist so we list the
%% values in reverse order so that it gets reversed properly.
headers_to_iolist(Tail, [<<"\r\n">>, V, <<": ">>, N|Acc]).
%% @doc Return the closing delimiter of the multipart message.
-spec close(binary()) -> iodata().
close(Boundary) ->
[<<"\r\n--">>, Boundary, <<"--">>].
-ifdef(TEST).
build_test() ->
Result = string:to_lower(binary_to_list(?TEST1_MIME)),
Result = string:to_lower(binary_to_list(iolist_to_binary([
<<"This is a message with multiple parts in MIME format.\r\n">>,
first_part(?TEST1_BOUNDARY, [{<<"content-type">>, <<"text/plain">>}]),
<<"This is the body of the message.">>,
part(?TEST1_BOUNDARY, [
{<<"content-type">>, <<"application/octet-stream">>},
{<<"content-transfer-encoding">>, <<"base64">>}]),
<<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
"Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
close(?TEST1_BOUNDARY)
]))),
ok.
identity_test() ->
B = boundary(),
Preamble = <<"This is a message with multiple parts in MIME format.">>,
H1 = [{<<"content-type">>, <<"text/plain">>}],
Body1 = <<"This is the body of the message.">>,
H2 = lists:sort([{<<"content-type">>, <<"application/octet-stream">>},
{<<"content-transfer-encoding">>, <<"base64">>}]),
Body2 = <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
"Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
Epilogue = <<"Gotta go fast!">>,
M = iolist_to_binary([
Preamble,
part(B, H1), Body1,
part(B, H2), Body2,
close(B),
Epilogue
]),
{done, Preamble, M2} = parse_body(M, B),
{ok, H1, M3} = parse_headers(M2, B),
{done, Body1, M4} = parse_body(M3, B),
{ok, H2Unsorted, M5} = parse_headers(M4, B),
H2 = lists:sort(H2Unsorted),
{done, Body2, M6} = parse_body(M5, B),
{done, Epilogue} = parse_headers(M6, B),
ok.
-endif.
-ifdef(PERF).
perf_build_multipart() ->
B = boundary(),
[
<<"preamble\r\n">>,
first_part(B, [{<<"content-type">>, <<"text/plain">>}]),
<<"This is the body of the message.">>,
part(B, [
{<<"content-type">>, <<"application/octet-stream">>},
{<<"content-transfer-encoding">>, <<"base64">>}]),
<<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
"Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
close(B),
<<"epilogue">>
].
horse_build() ->
horse:repeat(50000,
perf_build_multipart()
).
-endif.
%% Headers.
%% @doc Convenience function for extracting information from headers
%% when parsing a multipart/form-data stream.
-spec form_data(headers())
-> {data, binary()}
| {file, binary(), binary(), binary(), binary()}.
form_data(Headers) ->
{_, DispositionBin} = lists:keyfind(<<"content-disposition">>, 1, Headers),
{<<"form-data">>, Params} = parse_content_disposition(DispositionBin),
{_, FieldName} = lists:keyfind(<<"name">>, 1, Params),
case lists:keyfind(<<"filename">>, 1, Params) of
false ->
{data, FieldName};
{_, Filename} ->
Type = case lists:keyfind(<<"content-type">>, 1, Headers) of
false -> <<"text/plain">>;
{_, T} -> T
end,
TransferEncoding = case lists:keyfind(
<<"content-transfer-encoding">>, 1, Headers) of
false -> <<"7bit">>;
{_, TE} -> TE
end,
{file, FieldName, Filename, Type, TransferEncoding}
end.
-ifdef(TEST).
form_data_test_() ->
Tests = [
{[{<<"content-disposition">>, <<"form-data; name=\"submit-name\"">>}],
{data, <<"submit-name">>}},
{[{<<"content-disposition">>,
<<"form-data; name=\"files\"; filename=\"file1.txt\"">>},
{<<"content-type">>, <<"text/x-plain">>}],
{file, <<"files">>, <<"file1.txt">>,
<<"text/x-plain">>, <<"7bit">>}}
],
[{lists:flatten(io_lib:format("~p", [V])),
fun() -> R = form_data(V) end} || {V, R} <- Tests].
-endif.
%% @todo parse_content_description
%% @todo parse_content_id
%% @doc Parse an RFC 2183 content-disposition value.
%% @todo Support RFC 2231.
-spec parse_content_disposition(binary())
-> {binary(), [{binary(), binary()}]}.
parse_content_disposition(Bin) ->
parse_cd_type(Bin, <<>>).
parse_cd_type(<<>>, Acc) ->
{Acc, []};
parse_cd_type(<< C, Rest/bits >>, Acc) ->
case C of
$; -> {Acc, parse_before_param(Rest, [])};
$\s -> {Acc, parse_before_param(Rest, [])};
$\t -> {Acc, parse_before_param(Rest, [])};
?INLINE_LOWERCASE(parse_cd_type, Rest, Acc)
end.
-ifdef(TEST).
parse_content_disposition_test_() ->
Tests = [
{<<"inline">>, {<<"inline">>, []}},
{<<"attachment">>, {<<"attachment">>, []}},
{<<"attachment; filename=genome.jpeg;"
" modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";">>,
{<<"attachment">>, [
{<<"filename">>, <<"genome.jpeg">>},
{<<"modification-date">>, <<"Wed, 12 Feb 1997 16:29:51 -0500">>}
]}},
{<<"form-data; name=\"user\"">>,
{<<"form-data">>, [{<<"name">>, <<"user">>}]}},
{<<"form-data; NAME=\"submit-name\"">>,
{<<"form-data">>, [{<<"name">>, <<"submit-name">>}]}},
{<<"form-data; name=\"files\"; filename=\"file1.txt\"">>,
{<<"form-data">>, [
{<<"name">>, <<"files">>},
{<<"filename">>, <<"file1.txt">>}
]}},
{<<"file; filename=\"file1.txt\"">>,
{<<"file">>, [{<<"filename">>, <<"file1.txt">>}]}},
{<<"file; filename=\"file2.gif\"">>,
{<<"file">>, [{<<"filename">>, <<"file2.gif">>}]}}
],
[{V, fun() -> R = parse_content_disposition(V) end} || {V, R} <- Tests].
-endif.
-ifdef(PERF).
horse_parse_content_disposition_attachment() ->
horse:repeat(100000,
parse_content_disposition(<<"attachment; filename=genome.jpeg;"
" modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";">>)
).
horse_parse_content_disposition_form_data() ->
horse:repeat(100000,
parse_content_disposition(
<<"form-data; name=\"files\"; filename=\"file1.txt\"">>)
).
horse_parse_content_disposition_inline() ->
horse:repeat(100000,
parse_content_disposition(<<"inline">>)
).
-endif.
%% @doc Parse an RFC 2045 content-transfer-encoding header.
-spec parse_content_transfer_encoding(binary()) -> binary().
parse_content_transfer_encoding(Bin) ->
?INLINE_LOWERCASE_BC(Bin).
-ifdef(TEST).
parse_content_transfer_encoding_test_() ->
Tests = [
{<<"7bit">>, <<"7bit">>},
{<<"7BIT">>, <<"7bit">>},
{<<"8bit">>, <<"8bit">>},
{<<"binary">>, <<"binary">>},
{<<"quoted-printable">>, <<"quoted-printable">>},
{<<"base64">>, <<"base64">>},
{<<"Base64">>, <<"base64">>},
{<<"BASE64">>, <<"base64">>},
{<<"bAsE64">>, <<"base64">>}
],
[{V, fun() -> R = parse_content_transfer_encoding(V) end}
|| {V, R} <- Tests].
-endif.
-ifdef(PERF).
horse_parse_content_transfer_encoding() ->
horse:repeat(100000,
parse_content_transfer_encoding(<<"QUOTED-PRINTABLE">>)
).
-endif.
%% @doc Parse an RFC 2045 content-type header.
-spec parse_content_type(binary())
-> {binary(), binary(), [{binary(), binary()}]}.
parse_content_type(Bin) ->
parse_ct_type(Bin, <<>>).
parse_ct_type(<< C, Rest/bits >>, Acc) ->
case C of
$/ -> parse_ct_subtype(Rest, Acc, <<>>);
?INLINE_LOWERCASE(parse_ct_type, Rest, Acc)
end.
parse_ct_subtype(<<>>, Type, Subtype) when Subtype =/= <<>> ->
{Type, Subtype, []};
parse_ct_subtype(<< C, Rest/bits >>, Type, Acc) ->
case C of
$; -> {Type, Acc, parse_before_param(Rest, [])};
$\s -> {Type, Acc, parse_before_param(Rest, [])};
$\t -> {Type, Acc, parse_before_param(Rest, [])};
?INLINE_LOWERCASE(parse_ct_subtype, Rest, Type, Acc)
end.
-ifdef(TEST).
parse_content_type_test_() ->
Tests = [
{<<"image/gif">>,
{<<"image">>, <<"gif">>, []}},
{<<"text/plain">>,
{<<"text">>, <<"plain">>, []}},
{<<"text/plain; charset=us-ascii">>,
{<<"text">>, <<"plain">>, [{<<"charset">>, <<"us-ascii">>}]}},
{<<"text/plain; charset=\"us-ascii\"">>,
{<<"text">>, <<"plain">>, [{<<"charset">>, <<"us-ascii">>}]}},
{<<"multipart/form-data; boundary=AaB03x">>,
{<<"multipart">>, <<"form-data">>,
[{<<"boundary">>, <<"AaB03x">>}]}},
{<<"multipart/mixed; boundary=BbC04y">>,
{<<"multipart">>, <<"mixed">>, [{<<"boundary">>, <<"BbC04y">>}]}},
{<<"multipart/mixed; boundary=--------">>,
{<<"multipart">>, <<"mixed">>, [{<<"boundary">>, <<"--------">>}]}},
{<<"application/x-horse; filename=genome.jpeg;"
" some-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"
" charset=us-ascii; empty=; number=12345">>,
{<<"application">>, <<"x-horse">>, [
{<<"filename">>, <<"genome.jpeg">>},
{<<"some-date">>, <<"Wed, 12 Feb 1997 16:29:51 -0500">>},
{<<"charset">>, <<"us-ascii">>},
{<<"empty">>, <<>>},
{<<"number">>, <<"12345">>}
]}}
],
[{V, fun() -> R = parse_content_type(V) end}
|| {V, R} <- Tests].
-endif.
-ifdef(PERF).
horse_parse_content_type_zero() ->
horse:repeat(100000,
parse_content_type(<<"text/plain">>)
).
horse_parse_content_type_one() ->
horse:repeat(100000,
parse_content_type(<<"text/plain; charset=\"us-ascii\"">>)
).
horse_parse_content_type_five() ->
horse:repeat(100000,
parse_content_type(<<"application/x-horse; filename=genome.jpeg;"
" some-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"
" charset=us-ascii; empty=; number=12345">>)
).
-endif.
%% @doc Parse RFC 2045 parameters.
parse_before_param(<<>>, Params) ->
lists:reverse(Params);
parse_before_param(<< C, Rest/bits >>, Params) ->
case C of
$; -> parse_before_param(Rest, Params);
$\s -> parse_before_param(Rest, Params);
$\t -> parse_before_param(Rest, Params);
?INLINE_LOWERCASE(parse_param_name, Rest, Params, <<>>)
end.
parse_param_name(<<>>, Params, Acc) ->
lists:reverse([{Acc, <<>>}|Params]);
parse_param_name(<< C, Rest/bits >>, Params, Acc) ->
case C of
$= -> parse_param_value(Rest, Params, Acc);
?INLINE_LOWERCASE(parse_param_name, Rest, Params, Acc)
end.
parse_param_value(<<>>, Params, Name) ->
lists:reverse([{Name, <<>>}|Params]);
parse_param_value(<< C, Rest/bits >>, Params, Name) ->
case C of
$" -> parse_param_quoted_value(Rest, Params, Name, <<>>);
$; -> parse_before_param(Rest, [{Name, <<>>}|Params]);
$\s -> parse_before_param(Rest, [{Name, <<>>}|Params]);
$\t -> parse_before_param(Rest, [{Name, <<>>}|Params]);
C -> parse_param_value(Rest, Params, Name, << C >>)
end.
parse_param_value(<<>>, Params, Name, Acc) ->
lists:reverse([{Name, Acc}|Params]);
parse_param_value(<< C, Rest/bits >>, Params, Name, Acc) ->
case C of
$; -> parse_before_param(Rest, [{Name, Acc}|Params]);
$\s -> parse_before_param(Rest, [{Name, Acc}|Params]);
$\t -> parse_before_param(Rest, [{Name, Acc}|Params]);
C -> parse_param_value(Rest, Params, Name, << Acc/binary, C >>)
end.
%% We expect a final $" so no need to test for <<>>.
parse_param_quoted_value(<< $\\, C, Rest/bits >>, Params, Name, Acc) ->
parse_param_quoted_value(Rest, Params, Name, << Acc/binary, C >>);
parse_param_quoted_value(<< $", Rest/bits >>, Params, Name, Acc) ->
parse_before_param(Rest, [{Name, Acc}|Params]);
parse_param_quoted_value(<< C, Rest/bits >>, Params, Name, Acc)
when C =/= $\r ->
parse_param_quoted_value(Rest, Params, Name, << Acc/binary, C >>).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_qs).
-export([parse_qs/1]).
-export([qs/1]).
-export([urldecode/1]).
-export([urlencode/1]).
-type qs_vals() :: [{binary(), binary() | true}].
%% @doc Parse an application/x-www-form-urlencoded string.
%%
%% The percent decoding is inlined to greatly improve the performance
%% by avoiding copying binaries twice (once for extracting, once for
%% decoding) instead of just extracting the proper representation.
-spec parse_qs(binary()) -> qs_vals().
parse_qs(B) ->
parse_qs_name(B, [], <<>>).
parse_qs_name(<< $%, H, L, Rest/bits >>, Acc, Name) ->
C = (unhex(H) bsl 4 bor unhex(L)),
parse_qs_name(Rest, Acc, << Name/bits, C >>);
parse_qs_name(<< $+, Rest/bits >>, Acc, Name) ->
parse_qs_name(Rest, Acc, << Name/bits, " " >>);
parse_qs_name(<< $=, Rest/bits >>, Acc, Name) when Name =/= <<>> ->
parse_qs_value(Rest, Acc, Name, <<>>);
parse_qs_name(<< $&, Rest/bits >>, Acc, Name) ->
case Name of
<<>> -> parse_qs_name(Rest, Acc, <<>>);
_ -> parse_qs_name(Rest, [{Name, true}|Acc], <<>>)
end;
parse_qs_name(<< C, Rest/bits >>, Acc, Name) when C =/= $%, C =/= $= ->
parse_qs_name(Rest, Acc, << Name/bits, C >>);
parse_qs_name(<<>>, Acc, Name) ->
case Name of
<<>> -> lists:reverse(Acc);
_ -> lists:reverse([{Name, true}|Acc])
end.
parse_qs_value(<< $%, H, L, Rest/bits >>, Acc, Name, Value) ->
C = (unhex(H) bsl 4 bor unhex(L)),
parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
parse_qs_value(<< $+, Rest/bits >>, Acc, Name, Value) ->
parse_qs_value(Rest, Acc, Name, << Value/bits, " " >>);
parse_qs_value(<< $&, Rest/bits >>, Acc, Name, Value) ->
parse_qs_name(Rest, [{Name, Value}|Acc], <<>>);
parse_qs_value(<< C, Rest/bits >>, Acc, Name, Value) when C =/= $% ->
parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
parse_qs_value(<<>>, Acc, Name, Value) ->
lists:reverse([{Name, Value}|Acc]).
-ifdef(TEST).
parse_qs_test_() ->
Tests = [
{<<>>, []},
{<<"&">>, []},
{<<"a">>, [{<<"a">>, true}]},
{<<"a&">>, [{<<"a">>, true}]},
{<<"&a">>, [{<<"a">>, true}]},
{<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
{<<"a&&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
{<<"a&b&">>, [{<<"a">>, true}, {<<"b">>, true}]},
{<<"=">>, error},
{<<"=b">>, error},
{<<"a=">>, [{<<"a">>, <<>>}]},
{<<"a=b">>, [{<<"a">>, <<"b">>}]},
{<<"a=&b=">>, [{<<"a">>, <<>>}, {<<"b">>, <<>>}]},
{<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>},
{<<"c">>, true}, {<<"d">>, <<"e">>}]},
{<<"a=b=c&d=e=f&g=h=i">>, [{<<"a">>, <<"b=c">>},
{<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}]},
{<<"+">>, [{<<" ">>, true}]},
{<<"+=+">>, [{<<" ">>, <<" ">>}]},
{<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]},
{<<"+a+=+b+&+c+=+d+">>, [{<<" a ">>, <<" b ">>},
{<<" c ">>, <<" d ">>}]},
{<<"a%20b=c%20d">>, [{<<"a b">>, <<"c d">>}]},
{<<"%25%26%3D=%25%26%3D&_-.=.-_">>, [{<<"%&=">>, <<"%&=">>},
{<<"_-.">>, <<".-_">>}]},
{<<"for=extend%2Franch">>, [{<<"for">>, <<"extend/ranch">>}]}
],
[{Qs, fun() ->
E = try parse_qs(Qs) of
R -> R
catch _:_ ->
error
end
end} || {Qs, E} <- Tests].
parse_qs_identity_test_() ->
Tests = [
<<"+">>,
<<"hl=en&q=erlang+cowboy">>,
<<"direction=desc&for=extend%2Franch&sort=updated&state=open">>,
<<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
"la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee2"
"60c0b2f2aaad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0."
"696.16&os=3&ov=&rs=vpl&k=cookies%7Csale%7Cbrowser%7Cm"
"ore%7Cprivacy%7Cstatistics%7Cactivities%7Cauction%7Ce"
"mail%7Cfree%7Cin...&t=112373&xt=5%7C61%7C0&tz=-1&ev=x"
"&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pid=536454"
".55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc=">>,
<<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
"236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.ht"
"m&re=http%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv"
"=3.0.14&os=1&ov=XP&k=cars%2Cford&rs=js&xt=5%7C22%7C23"
"4&tz=%2B180&tk=key1%3Dvalue1%7Ckey2%3Dvalue2&zl=4%2C5"
"%2C6&za=4&zu=competitor.com&ua=Mozilla%2F5.0+%28Windo"
"ws%3B+U%3B+Windows+NT+6.1%3B+en-US%29+AppleWebKit%2F5"
"34.13+%28KHTML%2C+like+Gecko%29+Chrome%2F9.0.597.98+S"
"afari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&ort"
"b-sid=521732&ortb-xt=IAB3&ortb-ugc=">>
],
[{V, fun() -> V = qs(parse_qs(V)) end} || V <- Tests].
-endif.
-ifdef(PERF).
horse_parse_qs_shorter() ->
horse:repeat(20000,
parse_qs(<<"hl=en&q=erlang%20cowboy">>)
).
horse_parse_qs_short() ->
horse:repeat(20000,
parse_qs(
<<"direction=desc&for=extend%2Franch&sort=updated&state=open">>)
).
horse_parse_qs_long() ->
horse:repeat(20000,
parse_qs(<<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
"la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee260c0b2f2a"
"aad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0.696.16&os=3&ov=&rs"
"=vpl&k=cookies%7Csale%7Cbrowser%7Cmore%7Cprivacy%7Cstatistics%"
"7Cactivities%7Cauction%7Cemail%7Cfree%7Cin...&t=112373&xt=5%7C"
"61%7C0&tz=-1&ev=x&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pi"
"d=536454.55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc"
"=">>)
).
horse_parse_qs_longer() ->
horse:repeat(20000,
parse_qs(<<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
"236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.htm&re=http"
"%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv=3.0.14&os=1&ov=XP"
"&k=cars%2cford&rs=js&xt=5%7c22%7c234&tz=%2b180&tk=key1%3Dvalue"
"1%7Ckey2%3Dvalue2&zl=4,5,6&za=4&zu=competitor.com&ua=Mozilla%2"
"F5.0%20(Windows%3B%20U%3B%20Windows%20NT%206.1%3B%20en-US)%20A"
"ppleWebKit%2F534.13%20(KHTML%2C%20like%20Gecko)%20Chrome%2F9.0"
".597.98%20Safari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&o"
"rtb-sid=521732&ortb-xt=IAB3&ortb-ugc=">>)
).
-endif.
%% @doc Build an application/x-www-form-urlencoded string.
-spec qs(qs_vals()) -> binary().
qs([]) ->
<<>>;
qs(L) ->
qs(L, <<>>).
qs([], Acc) ->
<< $&, Qs/bits >> = Acc,
Qs;
qs([{Name, true}|Tail], Acc) ->
Acc2 = urlencode(Name, << Acc/bits, $& >>),
qs(Tail, Acc2);
qs([{Name, Value}|Tail], Acc) ->
Acc2 = urlencode(Name, << Acc/bits, $& >>),
Acc3 = urlencode(Value, << Acc2/bits, $= >>),
qs(Tail, Acc3).
-define(QS_SHORTER, [
{<<"hl">>, <<"en">>},
{<<"q">>, <<"erlang cowboy">>}
]).
-define(QS_SHORT, [
{<<"direction">>, <<"desc">>},
{<<"for">>, <<"extend/ranch">>},
{<<"sort">>, <<"updated">>},
{<<"state">>, <<"open">>}
]).
-define(QS_LONG, [
{<<"i">>, <<"EWiIXmPj5gl6">>},
{<<"v">>, <<"QowBp0oDLQXdd4x_GwiywA">>},
{<<"ip">>, <<"98.20.31.81">>},
{<<"la">>, <<"en">>},
{<<"pg">>, <<"New8.undertonebrandsafe.com/"
"698a2525065ee260c0b2f2aaad89ab82">>},
{<<"re">>, <<>>},
{<<"sz">>, <<"1">>},
{<<"fc">>, <<"1">>},
{<<"fr">>, <<"140">>},
{<<"br">>, <<"3">>},
{<<"bv">>, <<"11.0.696.16">>},
{<<"os">>, <<"3">>},
{<<"ov">>, <<>>},
{<<"rs">>, <<"vpl">>},
{<<"k">>, <<"cookies|sale|browser|more|privacy|statistics|"
"activities|auction|email|free|in...">>},
{<<"t">>, <<"112373">>},
{<<"xt">>, <<"5|61|0">>},
{<<"tz">>, <<"-1">>},
{<<"ev">>, <<"x">>},
{<<"tk">>, <<>>},
{<<"za">>, <<"1">>},
{<<"ortb-za">>, <<"1">>},
{<<"zu">>, <<>>},
{<<"zl">>, <<>>},
{<<"ax">>, <<"U">>},
{<<"ay">>, <<"U">>},
{<<"ortb-pid">>, <<"536454.55">>},
{<<"ortb-sid">>, <<"112373.8">>},
{<<"seats">>, <<"999">>},
{<<"ortb-xt">>, <<"IAB24">>},
{<<"ortb-ugc">>, <<>>}
]).
-define(QS_LONGER, [
{<<"i">>, <<"9pQNskA">>},
{<<"v">>, <<"0ySQQd1F">>},
{<<"ev">>, <<"12345678">>},
{<<"t">>, <<"12345">>},
{<<"sz">>, <<"3">>},
{<<"ip">>, <<"67.58.236.89">>},
{<<"la">>, <<"en">>},
{<<"pg">>, <<"http://www.yahoo.com/page1.htm">>},
{<<"re">>, <<"http://search.google.com">>},
{<<"fc">>, <<"1">>},
{<<"fr">>, <<"1">>},
{<<"br">>, <<"2">>},
{<<"bv">>, <<"3.0.14">>},
{<<"os">>, <<"1">>},
{<<"ov">>, <<"XP">>},
{<<"k">>, <<"cars,ford">>},
{<<"rs">>, <<"js">>},
{<<"xt">>, <<"5|22|234">>},
{<<"tz">>, <<"+180">>},
{<<"tk">>, <<"key1=value1|key2=value2">>},
{<<"zl">>, <<"4,5,6">>},
{<<"za">>, <<"4">>},
{<<"zu">>, <<"competitor.com">>},
{<<"ua">>, <<"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) "
"AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 "
"Safari/534.13">>},
{<<"ortb-za">>, <<"1,6,13">>},
{<<"ortb-pid">>, <<"521732">>},
{<<"ortb-sid">>, <<"521732">>},
{<<"ortb-xt">>, <<"IAB3">>},
{<<"ortb-ugc">>, <<>>}
]).
-ifdef(TEST).
qs_test_() ->
Tests = [
{[<<"a">>], error},
{[{<<"a">>, <<"b">>, <<"c">>}], error},
{[], <<>>},
{[{<<"a">>, true}], <<"a">>},
{[{<<"a">>, true}, {<<"b">>, true}], <<"a&b">>},
{[{<<"a">>, <<>>}], <<"a=">>},
{[{<<"a">>, <<"b">>}], <<"a=b">>},
{[{<<"a">>, <<>>}, {<<"b">>, <<>>}], <<"a=&b=">>},
{[{<<"a">>, <<"b">>}, {<<"c">>, true}, {<<"d">>, <<"e">>}],
<<"a=b&c&d=e">>},
{[{<<"a">>, <<"b=c">>}, {<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}],
<<"a=b%3Dc&d=e%3Df&g=h%3Di">>},
{[{<<" ">>, true}], <<"+">>},
{[{<<" ">>, <<" ">>}], <<"+=+">>},
{[{<<"a b">>, <<"c d">>}], <<"a+b=c+d">>},
{[{<<" a ">>, <<" b ">>}, {<<" c ">>, <<" d ">>}],
<<"+a+=+b+&+c+=+d+">>},
{[{<<"%&=">>, <<"%&=">>}, {<<"_-.">>, <<".-_">>}],
<<"%25%26%3D=%25%26%3D&_-.=.-_">>},
{[{<<"for">>, <<"extend/ranch">>}], <<"for=extend%2Franch">>}
],
[{lists:flatten(io_lib:format("~p", [Vals])), fun() ->
E = try qs(Vals) of
R -> R
catch _:_ ->
error
end
end} || {Vals, E} <- Tests].
qs_identity_test_() ->
Tests = [
[{<<"+">>, true}],
?QS_SHORTER,
?QS_SHORT,
?QS_LONG,
?QS_LONGER
],
[{lists:flatten(io_lib:format("~p", [V])), fun() ->
V = parse_qs(qs(V))
end} || V <- Tests].
-endif.
-ifdef(PERF).
horse_qs_shorter() ->
horse:repeat(20000, qs(?QS_SHORTER)).
horse_qs_short() ->
horse:repeat(20000, qs(?QS_SHORT)).
horse_qs_long() ->
horse:repeat(20000, qs(?QS_LONG)).
horse_qs_longer() ->
horse:repeat(20000, qs(?QS_LONGER)).
-endif.
%% @doc Decode a percent encoded string (x-www-form-urlencoded rules).
-spec urldecode(B) -> B when B::binary().
urldecode(B) ->
urldecode(B, <<>>).
urldecode(<< $%, H, L, Rest/bits >>, Acc) ->
C = (unhex(H) bsl 4 bor unhex(L)),
urldecode(Rest, << Acc/bits, C >>);
urldecode(<< $+, Rest/bits >>, Acc) ->
urldecode(Rest, << Acc/bits, " " >>);
urldecode(<< C, Rest/bits >>, Acc) when C =/= $% ->
urldecode(Rest, << Acc/bits, C >>);
urldecode(<<>>, Acc) ->
Acc.
unhex($0) -> 0;
unhex($1) -> 1;
unhex($2) -> 2;
unhex($3) -> 3;
unhex($4) -> 4;
unhex($5) -> 5;
unhex($6) -> 6;
unhex($7) -> 7;
unhex($8) -> 8;
unhex($9) -> 9;
unhex($A) -> 10;
unhex($B) -> 11;
unhex($C) -> 12;
unhex($D) -> 13;
unhex($E) -> 14;
unhex($F) -> 15;
unhex($a) -> 10;
unhex($b) -> 11;
unhex($c) -> 12;
unhex($d) -> 13;
unhex($e) -> 14;
unhex($f) -> 15.
-ifdef(TEST).
urldecode_test_() ->
Tests = [
{<<"%20">>, <<" ">>},
{<<"+">>, <<" ">>},
{<<"%00">>, <<0>>},
{<<"%fF">>, <<255>>},
{<<"123">>, <<"123">>},
{<<"%i5">>, error},
{<<"%5">>, error}
],
[{Qs, fun() ->
E = try urldecode(Qs) of
R -> R
catch _:_ ->
error
end
end} || {Qs, E} <- Tests].
urldecode_identity_test_() ->
Tests = [
<<"+">>,
<<"nothingnothingnothingnothing">>,
<<"Small+fast+modular+HTTP+server">>,
<<"Small%2C+fast%2C+modular+HTTP+server.">>,
<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
"%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
"%BE%8B%E3%80%9C">>
],
[{V, fun() -> V = urlencode(urldecode(V)) end} || V <- Tests].
-endif.
-ifdef(PERF).
horse_urldecode() ->
horse:repeat(100000,
urldecode(<<"nothingnothingnothingnothing">>)
).
horse_urldecode_plus() ->
horse:repeat(100000,
urldecode(<<"Small+fast+modular+HTTP+server">>)
).
horse_urldecode_hex() ->
horse:repeat(100000,
urldecode(<<"Small%2C%20fast%2C%20modular%20HTTP%20server.">>)
).
horse_urldecode_jp_hex() ->
horse:repeat(100000,
urldecode(<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
"%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
"%BE%8B%E3%80%9C">>)
).
horse_urldecode_mix() ->
horse:repeat(100000,
urldecode(<<"Small%2C+fast%2C+modular+HTTP+server.">>)
).
-endif.
%% @doc Percent encode a string (x-www-form-urlencoded rules).
-spec urlencode(B) -> B when B::binary().
urlencode(B) ->
urlencode(B, <<>>).
urlencode(<< $\s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $+ >>);
urlencode(<< $-, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $- >>);
urlencode(<< $., Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $. >>);
urlencode(<< $0, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $0 >>);
urlencode(<< $1, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $1 >>);
urlencode(<< $2, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $2 >>);
urlencode(<< $3, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $3 >>);
urlencode(<< $4, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $4 >>);
urlencode(<< $5, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $5 >>);
urlencode(<< $6, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $6 >>);
urlencode(<< $7, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $7 >>);
urlencode(<< $8, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $8 >>);
urlencode(<< $9, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $9 >>);
urlencode(<< $A, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $A >>);
urlencode(<< $B, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $B >>);
urlencode(<< $C, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $C >>);
urlencode(<< $D, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $D >>);
urlencode(<< $E, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $E >>);
urlencode(<< $F, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $F >>);
urlencode(<< $G, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $G >>);
urlencode(<< $H, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $H >>);
urlencode(<< $I, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $I >>);
urlencode(<< $J, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $J >>);
urlencode(<< $K, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $K >>);
urlencode(<< $L, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $L >>);
urlencode(<< $M, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $M >>);
urlencode(<< $N, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $N >>);
urlencode(<< $O, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $O >>);
urlencode(<< $P, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $P >>);
urlencode(<< $Q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Q >>);
urlencode(<< $R, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $R >>);
urlencode(<< $S, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $S >>);
urlencode(<< $T, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $T >>);
urlencode(<< $U, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $U >>);
urlencode(<< $V, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $V >>);
urlencode(<< $W, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $W >>);
urlencode(<< $X, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $X >>);
urlencode(<< $Y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Y >>);
urlencode(<< $Z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Z >>);
urlencode(<< $_, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $_ >>);
urlencode(<< $a, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $a >>);
urlencode(<< $b, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $b >>);
urlencode(<< $c, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $c >>);
urlencode(<< $d, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $d >>);
urlencode(<< $e, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $e >>);
urlencode(<< $f, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $f >>);
urlencode(<< $g, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $g >>);
urlencode(<< $h, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $h >>);
urlencode(<< $i, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $i >>);
urlencode(<< $j, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $j >>);
urlencode(<< $k, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $k >>);
urlencode(<< $l, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $l >>);
urlencode(<< $m, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $m >>);
urlencode(<< $n, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $n >>);
urlencode(<< $o, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $o >>);
urlencode(<< $p, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $p >>);
urlencode(<< $q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $q >>);
urlencode(<< $r, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $r >>);
urlencode(<< $s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $s >>);
urlencode(<< $t, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $t >>);
urlencode(<< $u, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $u >>);
urlencode(<< $v, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $v >>);
urlencode(<< $w, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $w >>);
urlencode(<< $x, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $x >>);
urlencode(<< $y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $y >>);
urlencode(<< $z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $z >>);
urlencode(<< C, Rest/bits >>, Acc) ->
H = hex(C bsr 4),
L = hex(C band 16#0f),
urlencode(Rest, << Acc/bits, $%, H, L >>);
urlencode(<<>>, Acc) ->
Acc.
hex( 0) -> $0;
hex( 1) -> $1;
hex( 2) -> $2;
hex( 3) -> $3;
hex( 4) -> $4;
hex( 5) -> $5;
hex( 6) -> $6;
hex( 7) -> $7;
hex( 8) -> $8;
hex( 9) -> $9;
hex(10) -> $A;
hex(11) -> $B;
hex(12) -> $C;
hex(13) -> $D;
hex(14) -> $E;
hex(15) -> $F.
-ifdef(TEST).
urlencode_test_() ->
Tests = [
{<<255, 0>>, <<"%FF%00">>},
{<<255, " ">>, <<"%FF+">>},
{<<" ">>, <<"+">>},
{<<"aBc123">>, <<"aBc123">>},
{<<".-_">>, <<".-_">>}
],
[{V, fun() -> E = urlencode(V) end} || {V, E} <- Tests].
urlencode_identity_test_() ->
Tests = [
<<"+">>,
<<"nothingnothingnothingnothing">>,
<<"Small fast modular HTTP server">>,
<<"Small, fast, modular HTTP server.">>,
<<227,131,132,227,130,164,227,131,179,227,130,189,227,
130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
129,153,227,130,139,230,151,139,229,190,139,227,128,156>>
],
[{V, fun() -> V = urldecode(urlencode(V)) end} || V <- Tests].
-endif.
-ifdef(PERF).
horse_urlencode() ->
horse:repeat(100000,
urlencode(<<"nothingnothingnothingnothing">>)
).
horse_urlencode_plus() ->
horse:repeat(100000,
urlencode(<<"Small fast modular HTTP server">>)
).
horse_urlencode_jp() ->
horse:repeat(100000,
urlencode(<<227,131,132,227,130,164,227,131,179,227,130,189,227,
130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
129,153,227,130,139,230,151,139,229,190,139,227,128,156>>)
).
horse_urlencode_mix() ->
horse:repeat(100000,
urlencode(<<"Small, fast, modular HTTP server.">>)
).
-endif.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cow_spdy).
%% Zstream.
-export([deflate_init/0]).
-export([inflate_init/0]).
%% Parse.
-export([split/1]).
-export([parse/2]).
%% Build.
-export([data/3]).
-export([syn_stream/12]).
-export([syn_reply/6]).
-export([rst_stream/2]).
%% @todo settings
-export([ping/1]).
-export([goaway/2]).
%% @todo headers
%% @todo window_update
-include("cow_spdy.hrl").
%% Zstream.
deflate_init() ->
Zdef = zlib:open(),
ok = zlib:deflateInit(Zdef),
_ = zlib:deflateSetDictionary(Zdef, ?ZDICT),
Zdef.
inflate_init() ->
Zinf = zlib:open(),
ok = zlib:inflateInit(Zinf),
Zinf.
%% Parse.
split(Data = << _:40, Length:24, _/bits >>)
when byte_size(Data) >= Length + 8 ->
Length2 = Length + 8,
<< Frame:Length2/binary, Rest/bits >> = Data,
{true, Frame, Rest};
split(_) ->
false.
parse(<< 0:1, StreamID:31, 0:7, IsFinFlag:1, _:24, Data/bits >>, _) ->
{data, StreamID, from_flag(IsFinFlag), Data};
parse(<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
_:25, StreamID:31, _:1, AssocToStreamID:31, Priority:3, _:5,
0:8, Rest/bits >>, Zinf) ->
case parse_headers(Rest, Zinf) of
{ok, Headers, [{<<":host">>, Host}, {<<":method">>, Method},
{<<":path">>, Path}, {<<":scheme">>, Scheme},
{<<":version">>, Version}]} ->
{syn_stream, StreamID, AssocToStreamID, from_flag(IsFinFlag),
from_flag(IsUnidirectionalFlag), Priority, Method,
Scheme, Host, Path, Version, Headers};
_ ->
{error, badprotocol}
end;
parse(<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, _:25,
StreamID:31, Rest/bits >>, Zinf) ->
case parse_headers(Rest, Zinf) of
{ok, Headers, [{<<":status">>, Status}, {<<":version">>, Version}]} ->
{syn_reply, StreamID, from_flag(IsFinFlag),
Status, Version, Headers};
_ ->
{error, badprotocol}
end;
parse(<< 1:1, 3:15, 3:16, 0:8, _:56, StatusCode:32 >>, _)
when StatusCode =:= 0; StatusCode > 11 ->
{error, badprotocol};
parse(<< 1:1, 3:15, 3:16, 0:8, _:25, StreamID:31, StatusCode:32 >>, _) ->
Status = case StatusCode of
1 -> protocol_error;
2 -> invalid_stream;
3 -> refused_stream;
4 -> unsupported_version;
5 -> cancel;
6 -> internal_error;
7 -> flow_control_error;
8 -> stream_in_use;
9 -> stream_already_closed;
10 -> invalid_credentials;
11 -> frame_too_large
end,
{rst_stream, StreamID, Status};
parse(<< 1:1, 3:15, 4:16, 0:7, ClearSettingsFlag:1, _:24,
NbEntries:32, Rest/bits >>, _) ->
try
Settings = [begin
Is0 = 0,
Key = case ID of
1 -> upload_bandwidth;
2 -> download_bandwidth;
3 -> round_trip_time;
4 -> max_concurrent_streams;
5 -> current_cwnd;
6 -> download_retrans_rate;
7 -> initial_window_size;
8 -> client_certificate_vector_size
end,
{Key, Value, from_flag(PersistFlag), from_flag(WasPersistedFlag)}
end || << Is0:6, WasPersistedFlag:1, PersistFlag:1,
ID:24, Value:32 >> <= Rest],
NbEntries = length(Settings),
{settings, from_flag(ClearSettingsFlag), Settings}
catch _:_ ->
{error, badprotocol}
end;
parse(<< 1:1, 3:15, 6:16, 0:8, _:24, PingID:32 >>, _) ->
{ping, PingID};
parse(<< 1:1, 3:15, 7:16, 0:8, _:56, StatusCode:32 >>, _)
when StatusCode > 2 ->
{error, badprotocol};
parse(<< 1:1, 3:15, 7:16, 0:8, _:25, LastGoodStreamID:31,
StatusCode:32 >>, _) ->
Status = case StatusCode of
0 -> ok;
1 -> protocol_error;
2 -> internal_error
end,
{goaway, LastGoodStreamID, Status};
parse(<< 1:1, 3:15, 8:16, 0:7, IsFinFlag:1, _:25, StreamID:31,
Rest/bits >>, Zinf) ->
case parse_headers(Rest, Zinf) of
{ok, Headers, []} ->
{headers, StreamID, from_flag(IsFinFlag), Headers};
_ ->
{error, badprotocol}
end;
parse(<< 1:1, 3:15, 9:16, 0:8, _:57, 0:31 >>, _) ->
{error, badprotocol};
parse(<< 1:1, 3:15, 9:16, 0:8, _:25, StreamID:31,
_:1, DeltaWindowSize:31 >>, _) ->
{window_update, StreamID, DeltaWindowSize};
parse(_, _) ->
{error, badprotocol}.
parse_headers(Data, Zinf) ->
[<< NbHeaders:32, Rest/bits >>] = inflate(Zinf, Data),
parse_headers(Rest, NbHeaders, [], []).
parse_headers(<<>>, 0, Headers, SpHeaders) ->
{ok, lists:reverse(Headers), lists:sort(SpHeaders)};
parse_headers(<<>>, _, _, _) ->
error;
parse_headers(_, 0, _, _) ->
error;
parse_headers(<< 0:32, _/bits >>, _, _, _) ->
error;
parse_headers(<< L1:32, Key:L1/binary, L2:32, Value:L2/binary, Rest/bits >>,
NbHeaders, Acc, SpAcc) ->
case Key of
<< $:, _/bits >> ->
parse_headers(Rest, NbHeaders - 1, Acc,
lists:keystore(Key, 1, SpAcc, {Key, Value}));
_ ->
parse_headers(Rest, NbHeaders - 1, [{Key, Value}|Acc], SpAcc)
end.
inflate(Zinf, Data) ->
try
zlib:inflate(Zinf, Data)
catch _:_ ->
ok = zlib:inflateSetDictionary(Zinf, ?ZDICT),
zlib:inflate(Zinf, <<>>)
end.
from_flag(0) -> false;
from_flag(1) -> true.
%% Build.
data(StreamID, IsFin, Data) ->
IsFinFlag = to_flag(IsFin),
Length = iolist_size(Data),
[<< 0:1, StreamID:31, 0:7, IsFinFlag:1, Length:24 >>, Data].
syn_stream(Zdef, StreamID, AssocToStreamID, IsFin, IsUnidirectional,
Priority, Method, Scheme, Host, Path, Version, Headers) ->
IsFinFlag = to_flag(IsFin),
IsUnidirectionalFlag = to_flag(IsUnidirectional),
HeaderBlock = build_headers(Zdef, [
{<<":method">>, Method},
{<<":scheme">>, Scheme},
{<<":host">>, Host},
{<<":path">>, Path},
{<<":version">>, Version}
|Headers]),
Length = 10 + iolist_size(HeaderBlock),
[<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
Length:24, 0:1, StreamID:31, 0:1, AssocToStreamID:31,
Priority:3, 0:5, 0:8 >>, HeaderBlock].
syn_reply(Zdef, StreamID, IsFin, Status, Version, Headers) ->
IsFinFlag = to_flag(IsFin),
HeaderBlock = build_headers(Zdef, [
{<<":status">>, Status},
{<<":version">>, Version}
|Headers]),
Length = 4 + iolist_size(HeaderBlock),
[<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, Length:24,
0:1, StreamID:31 >>, HeaderBlock].
rst_stream(StreamID, Status) ->
StatusCode = case Status of
protocol_error -> 1;
invalid_stream -> 2;
refused_stream -> 3;
unsupported_version -> 4;
cancel -> 5;
internal_error -> 6;
flow_control_error -> 7;
stream_in_use -> 8;
stream_already_closed -> 9;
invalid_credentials -> 10;
frame_too_large -> 11
end,
<< 1:1, 3:15, 3:16, 0:8, 8:24,
0:1, StreamID:31, StatusCode:32 >>.
%% @todo settings
ping(PingID) ->
<< 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>.
goaway(LastGoodStreamID, Status) ->
StatusCode = case Status of
ok -> 0;
protocol_error -> 1;
internal_error -> 2
end,
<< 1:1, 3:15, 7:16, 0:8, 8:24,
0:1, LastGoodStreamID:31, StatusCode:32 >>.
%% @todo headers
%% @todo window_update
build_headers(Zdef, Headers) ->
NbHeaders = length(Headers),
Headers2 = [begin
L1 = iolist_size(Key),
L2 = iolist_size(Value),
[<< L1:32 >>, Key, << L2:32 >>, Value]
end || {Key, Value} <- Headers],
zlib:deflate(Zdef, [<< NbHeaders:32 >>, Headers2], full).
to_flag(false) -> 0;
to_flag(true) -> 1.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | %% Zlib dictionary. -define(ZDICT, << 16#00, 16#00, 16#00, 16#07, 16#6f, 16#70, 16#74, 16#69, 16#6f, 16#6e, 16#73, 16#00, 16#00, 16#00, 16#04, 16#68, 16#65, 16#61, 16#64, 16#00, 16#00, 16#00, 16#04, 16#70, 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#03, 16#70, 16#75, 16#74, 16#00, 16#00, 16#00, 16#06, 16#64, 16#65, 16#6c, 16#65, 16#74, 16#65, 16#00, 16#00, 16#00, 16#05, 16#74, 16#72, 16#61, 16#63, 16#65, 16#00, 16#00, 16#00, 16#06, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#00, 16#00, 16#00, 16#0e, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00, 16#00, 16#00, 16#0d, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#73, 16#00, 16#00, 16#00, 16#03, 16#61, 16#67, 16#65, 16#00, 16#00, 16#00, 16#05, 16#61, 16#6c, 16#6c, 16#6f, 16#77, 16#00, 16#00, 16#00, 16#0d, 16#61, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#61, 16#63, 16#68, 16#65, 16#2d, 16#63, 16#6f, 16#6e, 16#74, 16#72, 16#6f, 16#6c, 16#00, 16#00, 16#00, 16#0a, 16#63, 16#6f, 16#6e, 16#6e, 16#65, 16#63, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#62, 16#61, 16#73, 16#65, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00, 16#00, 16#00, 16#0e, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#65, 16#6e, 16#67, 16#74, 16#68, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#6f, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0b, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6d, 16#64, 16#35, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#74, 16#79, 16#70, 16#65, 16#00, 16#00, 16#00, 16#04, 16#64, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00, 16#04, 16#65, 16#74, 16#61, 16#67, 16#00, 16#00, 16#00, 16#06, 16#65, 16#78, 16#70, 16#65, 16#63, 16#74, 16#00, 16#00, 16#00, 16#07, 16#65, 16#78, 16#70, 16#69, 16#72, 16#65, 16#73, 16#00, 16#00, 16#00, 16#04, 16#66, 16#72, 16#6f, 16#6d, 16#00, 16#00, 16#00, 16#04, 16#68, 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#08, 16#69, 16#66, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00, 16#11, 16#69, 16#66, 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d, 16#69, 16#66, 16#2d, 16#6e, 16#6f, 16#6e, 16#65, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00, 16#08, 16#69, 16#66, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00, 16#13, 16#69, 16#66, 16#2d, 16#75, 16#6e, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d, 16#6c, 16#61, 16#73, 16#74, 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#00, 16#00, 16#00, 16#08, 16#6c, 16#6f, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#0c, 16#6d, 16#61, 16#78, 16#2d, 16#66, 16#6f, 16#72, 16#77, 16#61, 16#72, 16#64, 16#73, 16#00, 16#00, 16#00, 16#06, 16#70, 16#72, 16#61, 16#67, 16#6d, 16#61, 16#00, 16#00, 16#00, 16#12, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00, 16#13, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#05, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00, 16#07, 16#72, 16#65, 16#66, 16#65, 16#72, 16#65, 16#72, 16#00, 16#00, 16#00, 16#0b, 16#72, 16#65, 16#74, 16#72, 16#79, 16#2d, 16#61, 16#66, 16#74, 16#65, 16#72, 16#00, 16#00, 16#00, 16#06, 16#73, 16#65, 16#72, 16#76, 16#65, 16#72, 16#00, 16#00, 16#00, 16#02, 16#74, 16#65, 16#00, 16#00, 16#00, 16#07, 16#74, 16#72, 16#61, 16#69, 16#6c, 16#65, 16#72, 16#00, 16#00, 16#00, 16#11, 16#74, 16#72, 16#61, 16#6e, 16#73, 16#66, 16#65, 16#72, 16#2d, 16#65, 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#07, 16#75, 16#70, 16#67, 16#72, 16#61, 16#64, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#75, 16#73, 16#65, 16#72, 16#2d, 16#61, 16#67, 16#65, 16#6e, 16#74, 16#00, 16#00, 16#00, 16#04, 16#76, 16#61, 16#72, 16#79, 16#00, 16#00, 16#00, 16#03, 16#76, 16#69, 16#61, 16#00, 16#00, 16#00, 16#07, 16#77, 16#61, 16#72, 16#6e, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#77, 16#77, 16#77, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00, 16#06, 16#6d, 16#65, 16#74, 16#68, 16#6f, 16#64, 16#00, 16#00, 16#00, 16#03, 16#67, 16#65, 16#74, 16#00, 16#00, 16#00, 16#06, 16#73, 16#74, 16#61, 16#74, 16#75, 16#73, 16#00, 16#00, 16#00, 16#06, 16#32, 16#30, 16#30, 16#20, 16#4f, 16#4b, 16#00, 16#00, 16#00, 16#07, 16#76, 16#65, 16#72, 16#73, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#08, 16#48, 16#54, 16#54, 16#50, 16#2f, 16#31, 16#2e, 16#31, 16#00, 16#00, 16#00, 16#03, 16#75, 16#72, 16#6c, 16#00, 16#00, 16#00, 16#06, 16#70, 16#75, 16#62, 16#6c, 16#69, 16#63, 16#00, 16#00, 16#00, 16#0a, 16#73, 16#65, 16#74, 16#2d, 16#63, 16#6f, 16#6f, 16#6b, 16#69, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#6b, 16#65, 16#65, 16#70, 16#2d, 16#61, 16#6c, 16#69, 16#76, 16#65, 16#00, 16#00, 16#00, 16#06, 16#6f, 16#72, 16#69, 16#67, 16#69, 16#6e, 16#31, 16#30, 16#30, 16#31, 16#30, 16#31, 16#32, 16#30, 16#31, 16#32, 16#30, 16#32, 16#32, 16#30, 16#35, 16#32, 16#30, 16#36, 16#33, 16#30, 16#30, 16#33, 16#30, 16#32, 16#33, 16#30, 16#33, 16#33, 16#30, 16#34, 16#33, 16#30, 16#35, 16#33, 16#30, 16#36, 16#33, 16#30, 16#37, 16#34, 16#30, 16#32, 16#34, 16#30, 16#35, 16#34, 16#30, 16#36, 16#34, 16#30, 16#37, 16#34, 16#30, 16#38, 16#34, 16#30, 16#39, 16#34, 16#31, 16#30, 16#34, 16#31, 16#31, 16#34, 16#31, 16#32, 16#34, 16#31, 16#33, 16#34, 16#31, 16#34, 16#34, 16#31, 16#35, 16#34, 16#31, 16#36, 16#34, 16#31, 16#37, 16#35, 16#30, 16#32, 16#35, 16#30, 16#34, 16#35, 16#30, 16#35, 16#32, 16#30, 16#33, 16#20, 16#4e, 16#6f, 16#6e, 16#2d, 16#41, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#74, 16#61, 16#74, 16#69, 16#76, 16#65, 16#20, 16#49, 16#6e, 16#66, 16#6f, 16#72, 16#6d, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#32, 16#30, 16#34, 16#20, 16#4e, 16#6f, 16#20, 16#43, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#33, 16#30, 16#31, 16#20, 16#4d, 16#6f, 16#76, 16#65, 16#64, 16#20, 16#50, 16#65, 16#72, 16#6d, 16#61, 16#6e, 16#65, 16#6e, 16#74, 16#6c, 16#79, 16#34, 16#30, 16#30, 16#20, 16#42, 16#61, 16#64, 16#20, 16#52, 16#65, 16#71, 16#75, 16#65, 16#73, 16#74, 16#34, 16#30, 16#31, 16#20, 16#55, 16#6e, 16#61, 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#65, 16#64, 16#34, 16#30, 16#33, 16#20, 16#46, 16#6f, 16#72, 16#62, 16#69, 16#64, 16#64, 16#65, 16#6e, 16#34, 16#30, 16#34, 16#20, 16#4e, 16#6f, 16#74, 16#20, 16#46, 16#6f, 16#75, 16#6e, 16#64, 16#35, 16#30, 16#30, 16#20, 16#49, 16#6e, 16#74, 16#65, 16#72, 16#6e, 16#61, 16#6c, 16#20, 16#53, 16#65, 16#72, 16#76, 16#65, 16#72, 16#20, 16#45, 16#72, 16#72, 16#6f, 16#72, 16#35, 16#30, 16#31, 16#20, 16#4e, 16#6f, 16#74, 16#20, 16#49, 16#6d, 16#70, 16#6c, 16#65, 16#6d, 16#65, 16#6e, 16#74, 16#65, 16#64, 16#35, 16#30, 16#33, 16#20, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, 16#55, 16#6e, 16#61, 16#76, 16#61, 16#69, 16#6c, 16#61, 16#62, 16#6c, 16#65, 16#4a, 16#61, 16#6e, 16#20, 16#46, 16#65, 16#62, 16#20, 16#4d, 16#61, 16#72, 16#20, 16#41, 16#70, 16#72, 16#20, 16#4d, 16#61, 16#79, 16#20, 16#4a, 16#75, 16#6e, 16#20, 16#4a, 16#75, 16#6c, 16#20, 16#41, 16#75, 16#67, 16#20, 16#53, 16#65, 16#70, 16#74, 16#20, 16#4f, 16#63, 16#74, 16#20, 16#4e, 16#6f, 16#76, 16#20, 16#44, 16#65, 16#63, 16#20, 16#30, 16#30, 16#3a, 16#30, 16#30, 16#3a, 16#30, 16#30, 16#20, 16#4d, 16#6f, 16#6e, 16#2c, 16#20, 16#54, 16#75, 16#65, 16#2c, 16#20, 16#57, 16#65, 16#64, 16#2c, 16#20, 16#54, 16#68, 16#75, 16#2c, 16#20, 16#46, 16#72, 16#69, 16#2c, 16#20, 16#53, 16#61, 16#74, 16#2c, 16#20, 16#53, 16#75, 16#6e, 16#2c, 16#20, 16#47, 16#4d, 16#54, 16#63, 16#68, 16#75, 16#6e, 16#6b, 16#65, 16#64, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#68, 16#74, 16#6d, 16#6c, 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#70, 16#6e, 16#67, 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#6a, 16#70, 16#67, 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#67, 16#69, 16#66, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78, 16#6d, 16#6c, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69, 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78, 16#68, 16#74, 16#6d, 16#6c, 16#2b, 16#78, 16#6d, 16#6c, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#70, 16#6c, 16#61, 16#69, 16#6e, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#6a, 16#61, 16#76, 16#61, 16#73, 16#63, 16#72, 16#69, 16#70, 16#74, 16#2c, 16#70, 16#75, 16#62, 16#6c, 16#69, 16#63, 16#70, 16#72, 16#69, 16#76, 16#61, 16#74, 16#65, 16#6d, 16#61, 16#78, 16#2d, 16#61, 16#67, 16#65, 16#3d, 16#67, 16#7a, 16#69, 16#70, 16#2c, 16#64, 16#65, 16#66, 16#6c, 16#61, 16#74, 16#65, 16#2c, 16#73, 16#64, 16#63, 16#68, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#75, 16#74, 16#66, 16#2d, 16#38, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#69, 16#73, 16#6f, 16#2d, 16#38, 16#38, 16#35, 16#39, 16#2d, 16#31, 16#2c, 16#75, 16#74, 16#66, 16#2d, 16#2c, 16#2a, 16#2c, 16#65, 16#6e, 16#71, 16#3d, 16#30, 16#2e >>). |
> > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
{application, cowlib, [
{description, "Support library for manipulating Web protocols."},
{vsn, "1.0.2"},
{id, "git"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib,
crypto
]}
]}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
## Changelog
## v1.0.3
* Enhancements
* Raise if new lines are used in header values
* Bug fix
* Allow mime type lookup of uppercase extensions
* Do not validate uppercase headers in production to avoid performance hits
* Prevent Plug.Parsers from clobbering existing conn.params when part of it is unfetched
## v1.0.2
* Bug fix
* Ensure cookie store returns a Session ID so they can be dropped
## v1.0.1
* Enhancements
* Allow configuring all options supported by the underlying transport (i.e. cowboy)
## v1.0.0
* Enhancements
* Allow custom headers in `Plug.Static`
* Bug fix
* No longer automatically assume "priv" for cert and key files for Cowboy SSL
* Raise if response has been sent more than once in test connection
* Raise when body is nil on `Plug.Conn.resp/3`
* Show more info and escape messages in `Plug.Debugger`
## v0.14.0
* Enhancements
* Support `:rewrite_on` on `Plug.SSL`
* Add `Plug.Conn.merge_resp_headers/2`
* Bug fix
* Ensure message encryptor and verifier do not error on bad data
## v0.13.1
* Enhancements
* Add `conn.request_path`
* Raise if `put_session/3` is invoked when response is sent
* Bug fixes
* Fix empty params being encoded into query string as '&'
* Deprecations
* `Plug.Conn.full_path/2` is deprecated in favor of `conn.request_path`
* `Plug.Test.put_req_header/3` and `Plug.Test.delete_req_header/3` is deprecated in favor of similarly named functions in `Plug.Conn`
## v0.13.0
* Enhancements
* Raise if a header in upcase is given
* Store timestamps in sessions ETS table and document each entry format
* Allow private options when specifying routes in `Plug.Router`
* Allow the session to be cleared and ignored when an invalid CSRF token is given
* Allow log level to be configured in `Plug.Logger`
* Generate masked CSRF tokens to avoid BREACH attacks
* Backwards incompatible changes
* `Plug.Logger` no longer sets the request id. Use `Plug.RequestId` instead
## v0.12.2
* Enhancements
* Add `Plug.HTML`
* Bug fixes
* Do not crash on poorly encoded cookies
* Decode parameters before matching on the router
## v0.12.1
* Enhancements
* Add `Plug.SSL` with redirection from HTTP and HSTS support
* Remove the need for `:encrypt` option from `Plug.Session.COOKIE`. The need for encryption can be fully specified by passing `:encrypted_salt` option. This improvement is backwards compatible.
* Bug fixes
* Ensure we don't parse body params if they were already parsed
## v0.12.0
* Enhancements
* Add `query_params` and `body_params` to keep query and body parameters apart from `params`
* Allow custom encoders when encoding query parameters
* Assert valid utf-8 on url encoded and multipart bodies
* Bug fixes
* Use only body parameters when detecting method override
* Add Vary header when serving gzipped content in Plug.Static
* Deprecations
* `fetch_params/2` is deprecated in favor of `fetch_query_params/2`
## v0.11.3
* Bug fixes
* Ensure test adapter reuses the given connection
* Deprecations
* The `:headers` option in `Plug.Test.conn/4` is deprecated in favor of `put_req_header/3`
## v0.11.2
* Enhancements
* Add `:log_on_halt` option to `Plug.Builder` and `Plug.Router`
* Use raw files and delayed writes on upload
* Bug fixes
* Do not read the whole request body at once
* Improve performance of url encoded params
* Deprecations
* `Plug.Builder.compile/1` and `Plug.Builder.compile/2` are deprecated in favor of explicit `Plug.Builder.compile/3`
## v0.11.1
* Enhancements
* Allow Plug mimes to be configured via application environment
* Extend JSON parser to be compatible with all json compatible content types. This includes types with suffix `+json`
* Add `Plug.Conn.clear_session/1`
* Bug fixes
* Do not require cowboy at compile time
* Also parse request bodies on DELETE requests
## v0.11.0
* Enhancements
* Add `Plug.Conn.async_assign/3` and `Plug.Conn.await_assign/3` to start and await for assigns asynchronously, mimic'ing `Task.async/1` and `Task.await/2` behaviour
* Add `Plug.Conn.WrapperError` to propagate an error with the connection for better debugging by either `Plug.Debugger` or `Plug.ErrorHandler`
* Add `Plug.Conn.update_resp_header/4` to update a response header or set its initial value if not present
* Bug fixes
* Skip parsing of files when no filename is sent
* Fix how script_name are accumulated with multiple calls to `Plug.Router.forward/2`
* Backwards incompatible changes
* `Plug.CSRFProtection` now uses a session to store tokens. Tokens are now generated on demand and can be accessed via `Plug.CSRFProtection.get_csrf_token/0`
## v0.10.0
* Enhancements
* Add `:only` option to `Plug.Static` to avoid all requests triggering file system queries
* Add ETag management to `Plug.Static` when requests to not contain a versioned query string
* Enforce atom or string keys in `Plug.Conn.put_session/3` and friends and normalize keys to strings
* Bug fixes
* Add UTF-8 tag to debugger templates
* Backwards incompatible changes
* `Plug.CSRFProtection` now uses a cookie instead of session and expects a `"_csrf_token"` parameter instead of `"csrf_token"`
## v0.9.0
* Enhancements
* Add `Plug.Conn.full_path/1`
* Add `Plug.CSRFProtection` that adds cross-forgery protection
* Add `Plug.ErrorHandler` that allows an error page to be sent on crashes (instead of a blank one)
* Support host option in `Plug.Router`
* Backwards incompatible changes
* Add `Plug.Router.Utils.build_match/1` was renamed to `build_path_match/1`
## v0.8.4
* Bug fixes
* Clean up `{:plug_conn, :sent}` messages from listener inbox and ensure connection works accross processes
* Deprecations
* Deprecate `recycle/2` in favor of `recycle_cookies` in Plug.Test
## v0.8.3
* Enhancements
* Use PKCS7 padding in MessageEncryptor (the same as OpenSSL)
* Add support for custom serializers in cookie session store
* Allow customization of key generation in cookie session store
* Automatically import `Plug.Conn` in Plug builder
* Render errors from Plug when using Ranch/Cowboy nicely
* Provide `Plug.Crypto.secure_compare/2` for comparing binaries
* Add `Plug.Debugger` for helpful pages whenever there is a failure during a request
* Deprecations
* Deprecate `:accept` in favor of `:pass` in Plug.Parsers
## v0.8.2
* Enhancements
* Add `Plug.Conn.Utils.media_type/1` to provide media type parsing with wildcard support
* Do not print adapter data by default when inspecting the connection
* Allow plug_status to simplify the definition of plug aware exceptions
* Allow cache headers in `Plug.Static` to be turned off
* Bug fix
* Support dots on header parameter parsing
## v0.8.1
* Enhancements
* Add a `Plug.Parsers.JSON` that expects a JSON decoder as argument
* Bug fix
* Properly populate `params` field for test connections
* Fix `Plug.Logger` not reporting the proper path
## v0.8.0
* Enhancements
* Add `fetch_session/2`, `fetch_params/2` and `fetch_cookies/2` so they can be pluggable
* Raise an error message on invalid router indentifiers
* Add `put_status/2` and support atom status codes
* Add `secret_key_base` field to the connection
* Backwards incompatible changes
* Add `encryption_salt` and `signing_salt` to the CookieStore and derive actual keys from `secret_key_base`
## v0.7.0
* Enhancements
* Support Elixir 1.0.0-rc1
* Support haltable pipelines with `Plug.Conn.halt/2`
* Ensure both Plug.Builder and Plug.Router's `call/2` are overridable
* Bug fix
* Properly report times in Logger
* Backwards incompatible changes
* Remove support for Plug wrappers
## v0.6.0
* Enhancements
* Add `Plug.Logger`
* Add `conn.peer` and `conn.remote_ip`
* Add `Plug.Conn.sendfile/5`
* Allow `call/2` from `Plug.Builder` to be overridable
## v0.5.3
* Enhancements
* Update to Cowboy v1.0.0
* Update mime types list
* Update to Elixir v0.15.0
## v0.5.2
* Enhancements
* Update to Elixir v0.14.3
* Cowboy adapter now returns `{:error,:eaddrinuse}` when port is already in use
* Add `Plug.Test.recycle/2` that copies relevant data between connections for future requests
## v0.5.1
* Enhancements
* Add ability to configure when `Plug.Parsers` raises `UnsupportedMediaTypeError`
* Add `Plug.Conn.Query.encode/1`
* Add `CookieStore` for session
* Bug fixes
* Ensure plug parses content-type with CRLF as LWS
## v0.5.0
* Enhancements
* Update to Elixir v0.14.0
* Update Cowboy adapter to v0.10.0
* Add `Plug.Conn.read_body/2`
* Backwards incompatible changes
* `Plug.Parsers` now expect `:length` instead of `:limit` and also accept `:read_length` and `:read_timeout`
## v0.4.4
* Enhancements
* Update to Elixir v0.13.3
## v0.4.3
* Enhancements
* Update to Elixir v0.13.2
## v0.4.2
* Enhancements
* Update to Elixir v0.13.1
## v0.4.1
* Enhancements
* Remove `:mime` dependency in favor of `Plug.MIME`
* Improve errors when Cowboy is not available
* First hex package release
## v0.4.0
* Enhancements
* Support `before_send/1` callbacks
* Add `Plug.Static`
* Allow iodata as the body
* Do not allow response headers to be set if the response was already sent
* Add `Plug.Conn.private` to be used as storage by libraries/frameworks
* Add `get_req_header` and `get_resp_header` for fetching request and response headers
* Backwards incompatible changes
* `Plug.Connection` was removed in favor of `Plug.Conn`
* `Plug.Conn` is now a struct
* assigns, cookies, params and sessions have been converted to maps
## v0.3.0
* Definition of the Plug specification
|
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 |
Copyright (c) 2013 Plataformatec.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# Plug
[](https://travis-ci.org/elixir-lang/plug)
[](http://inch-ci.org/github/elixir-lang/plug)
Plug is:
1. A specification for composable modules between web applications
2. Connection adapters for different web servers in the Erlang VM
[Documentation for Plug is available online](http://hexdocs.pm/plug/).
## Hello world
```elixir
defmodule MyPlug do
import Plug.Conn
def init(options) do
# initialize options
options
end
def call(conn, _opts) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello world")
end
end
```
The snippet above shows a very simple example on how to use Plug. Save that snippet to a file and run it inside the plug application with:
$ iex -S mix
iex> c "path/to/file.ex"
[MyPlug]
iex> {:ok, _} = Plug.Adapters.Cowboy.http MyPlug, []
{:ok, #PID<...>}
Access "http://localhost:4000/" and we are done!
## Installation
You can use plug in your projects in two steps:
1. Add plug and your webserver of choice (currently cowboy) to your `mix.exs` dependencies:
```elixir
def deps do
[{:cowboy, "~> 1.0.0"},
{:plug, "~> 1.0"}]
end
```
2. List both `:cowboy` and `:plug` as your application dependencies:
```elixir
def application do
[applications: [:cowboy, :plug]]
end
```
## The Plug.Conn
In the hello world example, we defined our first plug. What is a plug after all?
A plug takes two shapes. It is a function that receives a connection and a set of options as arguments and returns the connection or it is a module that provides an `init/1` function to initialize options and implement the `call/2` function, receiving the connection and the initialized options, and returning the connection.
As per the specification above, a connection is represented by the `Plug.Conn` struct:
```elixir
%Plug.Conn{host: "www.example.com",
path_info: ["bar", "baz"],
...}
```
Data can be read directly from the connection and also pattern matched on. Manipulating the connection often happens with the use of the functions defined in the `Plug.Conn` module. In our example, both `put_resp_content_type/2` and `send_resp/3` are defined in `Plug.Conn`.
Remember that, as everything else in Elixir, **a connection is immutable**, so every manipulation returns a new copy of the connection:
```elixir
conn = put_resp_content_type(conn, "text/plain")
conn = send_resp(conn, 200, "ok")
conn
```
Finally, keep in mind that a connection is a **direct interface to the underlying web server**. When you call `send_resp/3` above, it will immediately send the given status and body back to the client. This makes features like streaming a breeze to work with.
## The Plug Router
In practice, developers rarely write their own plugs. For example, Plug ships with a router that allows developers to quickly match on incoming requests and perform some action:
```elixir
defmodule AppRouter do
use Plug.Router
plug :match
plug :dispatch
get "/hello" do
send_resp(conn, 200, "world")
end
forward "/users", to: UsersRouter
match _ do
send_resp(conn, 404, "oops")
end
end
```
The router is a plug and, not only that, it contains its own plug pipeline too. The example above says that when the router is invoked, it will invoke the `:match` plug, represented by a local `match/2` function, and then call the `:dispatch` plug which will execute the matched code.
Plug ships with many plugs that you can add to the router plug pipeline, allowing you to plug something before a route matches or before a route is dispatched to. For example, if you want to add logging to the router, just do:
```elixir
plug Plug.Logger
plug :match
plug :dispatch
```
Note `Plug.Router` compiles all of your routes into a single function and relies on the Erlang VM to optimize the underlying routes into a tree lookup, instead of a linear lookup that would instead match route-per-route. This means route lookups are extremely fast in Plug!
This also means that a catch all `match` is recommended to be defined, as in the example above, otherwise routing fails with a function clause error (as it would in any regular Elixir function).
Each route needs to return the connection as per the Plug specification. See `Plug.Router` docs for more information.
## Testing plugs
Plug ships with a `Plug.Test` module that makes testing your plugs easy. Here is how we can test the router from above (or any other plug):
```elixir
defmodule MyPlugTest do
use ExUnit.Case, async: true
use Plug.Test
@opts AppRouter.init([])
test "returns hello world" do
# Create a test connection
conn = conn(:get, "/hello")
# Invoke the plug
conn = AppRouter.call(conn, @opts)
# Assert the response and status
assert conn.state == :sent
assert conn.status == 200
assert conn.resp_body == "world"
end
end
```
### Available Plugs
This project aims to ship with different plugs that can be re-used across applications:
* `Plug.CSRFProtection` - adds Cross-Site Request Forgery protection to your application. Typically required if you are using `Plug.Session`;
* `Plug.Head` - converts HEAD requests to GET requests;
* `Plug.Logger` - logs requests;
* `Plug.MethodOverride` - overrides a request method with one specified in headers;
* `Plug.Parsers` - responsible for parsing the request body given its content-type;
* `Plug.RequestId` - sets up a request ID to be used in logs;
* `Plug.Session` - handles session management and storage;
* `Plug.SSL` - enforce requests through SSL;
* `Plug.Static` - serves static files;
You can go into more details about each of them [in our docs](http://hexdocs.pm/plug/).
### Helper modules
Modules that can be used after you use `Plug.Router` or `Plug.Builder` to help development:
* `Plug.Debugger` - shows a helpful debugging page every time there is a failure in a request;
* `Plug.ErrorHandler` - allows developers to customize error pages in case of crashes instead of sending a blank one;
## License
Plug source code is released under Apache 2 License.
Check LICENSE file for more information.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
defmodule Plug do
@moduledoc """
The plug specification.
There are two kind of plugs: function plugs and module plugs.
#### Function plugs
A function plug is any function that receives a connection and a set of
options and returns a connection. Its type signature must be:
(Plug.Conn.t, Plug.opts) :: Plug.Conn.t
#### Module plugs
A module plug is an extension of the function plug. It is a module that must
export:
* a `call/2` function with the signature defined above
* an `init/1` function which takes a set of options and initializes it.
The result returned by `init/1` is passed as second argument to `call/2`. Note
that `init/1` may be called during compilation and as such it must not return
pids, ports or values that are not specific to the runtime.
The API expected by a module plug is defined as a behaviour by the
`Plug` module (this module).
## Examples
Here's an example of a function plug:
def json_header_plug(conn, opts) do
conn |> put_resp_content_type("application/json")
end
Here's an example of a module plug:
defmodule JSONHeaderPlug do
def init(opts) do
opts
end
def call(conn, _opts) do
conn |> put_resp_content_type("application/json")
end
end
## The Plug pipeline
The `Plug.Builder` module provides conveniences for building plug
pipelines.
"""
@type opts :: tuple | atom | integer | float | [opts]
use Behaviour
use Application
defcallback init(opts) :: opts
defcallback call(Plug.Conn.t, opts) :: Plug.Conn.t
@doc false
def start(_type, _args) do
Logger.add_translator {Plug.Adapters.Translator, :translate}
Plug.Supervisor.start_link()
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
defmodule Plug.Adapters.Cowboy do
@moduledoc """
Adapter interface to the Cowboy webserver.
## Options
* `:ip` - the ip to bind the server to.
Must be a tuple in the format `{x, y, z, w}`.
* `:port` - the port to run the server.
Defaults to 4000 (http) and 4040 (https).
* `:acceptors` - the number of acceptors for the listener.
Defaults to 100.
* `:max_connections` - max number of connections supported.
Defaults to `:infinity`.
* `:dispatch` - manually configure Cowboy's dispatch.
If this option is used, the given plug won't be initialized
nor dispatched to (and doing so becomes the user's responsibility).
* `:ref` - the reference name to be used.
Defaults to `plug.HTTP` (http) and `plug.HTTPS` (https).
This is the value that needs to be given on shutdown.
* `:compress` - Cowboy will attempt to compress the response body.
Defaults to false.
* `:timeout` - Time in ms with no requests before Cowboy closes the connection.
Defaults to 5000ms.
* `:protocol_options` - Specifies remaining protocol options,
see [Cowboy protocol docs](http://ninenines.eu/docs/en/cowboy/1.0/manual/cowboy_protocol/).
All other options are given to the underlying transport.
"""
# Made public with @doc false for testing.
@doc false
def args(scheme, plug, opts, cowboy_options) do
cowboy_options
|> Keyword.put_new(:ref, build_ref(plug, scheme))
|> Keyword.put_new(:dispatch, cowboy_options[:dispatch] || dispatch_for(plug, opts))
|> normalize_cowboy_options(scheme)
|> to_args()
end
@doc """
Run cowboy under http.
## Example
# Starts a new interface
Plug.Adapters.Cowboy.http MyPlug, [], port: 80
# The interface above can be shutdown with
Plug.Adapters.Cowboy.shutdown MyPlug.HTTP
"""
@spec http(module(), Keyword.t, Keyword.t) ::
{:ok, pid} | {:error, :eaddrinuse} | {:error, term}
def http(plug, opts, cowboy_options \\ []) do
run(:http, plug, opts, cowboy_options)
end
@doc """
Run cowboy under https.
Besides the options described in the module documentation,
this module also accepts all options defined in [the `ssl`
erlang module] (http://www.erlang.org/doc/man/ssl.html),
like keyfile, certfile, cacertfile and others.
The certificate files can be given as a relative path.
For such, the `:otp_app` option must also be given and
certificates will be looked from the priv directory of
the given application.
## Example
# Starts a new interface
Plug.Adapters.Cowboy.https MyPlug, [],
port: 443,
password: "SECRET",
otp_app: :my_app,
keyfile: "priv/ssl/key.pem",
certfile: "priv/ssl/cert.pem"
# The interface above can be shutdown with
Plug.Adapters.Cowboy.shutdown MyPlug.HTTPS
"""
@spec https(module(), Keyword.t, Keyword.t) ::
{:ok, pid} | {:error, :eaddrinuse} | {:error, term}
def https(plug, opts, cowboy_options \\ []) do
Application.ensure_all_started(:ssl)
run(:https, plug, opts, cowboy_options)
end
@doc """
Shutdowns the given reference.
"""
def shutdown(ref) do
:cowboy.stop_listener(ref)
end
@doc """
Returns a child spec to be supervised by your application.
"""
def child_spec(scheme, plug, opts, cowboy_options \\ []) do
[ref, nb_acceptors, trans_opts, proto_opts] = args(scheme, plug, opts, cowboy_options)
ranch_module = case scheme do
:http -> :ranch_tcp
:https -> :ranch_ssl
end
:ranch.child_spec(ref, nb_acceptors, ranch_module, trans_opts, :cowboy_protocol, proto_opts)
end
## Helpers
@http_cowboy_options [port: 4000]
@https_cowboy_options [port: 4040]
@protocol_options [:timeout, :compress]
defp run(scheme, plug, opts, cowboy_options) do
case Application.ensure_all_started(:cowboy) do
{:ok, _} ->
:ok
{:error, {:cowboy, _}} ->
raise "could not start the cowboy application. Please ensure it is listed " <>
"as a dependency both in deps and application in your mix.exs"
end
apply(:cowboy, :"start_#{scheme}", args(scheme, plug, opts, cowboy_options))
end
defp normalize_cowboy_options(cowboy_options, :http) do
Keyword.merge @http_cowboy_options, cowboy_options
end
defp normalize_cowboy_options(cowboy_options, :https) do
assert_ssl_options(cowboy_options)
cowboy_options = Keyword.merge @https_cowboy_options, cowboy_options
cowboy_options = Enum.reduce [:keyfile, :certfile, :cacertfile], cowboy_options, &normalize_ssl_file(&1, &2)
cowboy_options = Enum.reduce [:password], cowboy_options, &to_char_list(&2, &1)
cowboy_options
end
defp to_args(all_opts) do
{initial_transport_options, opts} = Enum.partition(all_opts, &is_atom/1)
opts = Keyword.delete(opts, :otp_app)
{ref, opts} = Keyword.pop(opts, :ref)
{dispatch, opts} = Keyword.pop(opts, :dispatch)
{acceptors, opts} = Keyword.pop(opts, :acceptors, 100)
{protocol_options, opts} = Keyword.pop(opts, :protocol_options, [])
dispatch = :cowboy_router.compile(dispatch)
{extra_options, transport_options} = Keyword.split(opts, @protocol_options)
protocol_options = [env: [dispatch: dispatch]] ++ protocol_options ++ extra_options
[ref, acceptors, initial_transport_options ++ transport_options, protocol_options]
end
defp build_ref(plug, scheme) do
Module.concat(plug, scheme |> to_string |> String.upcase)
end
defp dispatch_for(plug, opts) do
opts = plug.init(opts)
[{:_, [ {:_, Plug.Adapters.Cowboy.Handler, {plug, opts}} ]}]
end
defp normalize_ssl_file(key, cowboy_options) do
value = cowboy_options[key]
cond do
is_nil(value) ->
cowboy_options
Path.type(value) == :absolute ->
put_ssl_file cowboy_options, key, value
true ->
put_ssl_file cowboy_options, key, Path.expand(value, otp_app(cowboy_options))
end
end
defp assert_ssl_options(cowboy_options) do
unless Keyword.has_key?(cowboy_options, :key) or
Keyword.has_key?(cowboy_options, :keyfile) do
fail "missing option :key/:keyfile"
end
unless Keyword.has_key?(cowboy_options, :cert) or
Keyword.has_key?(cowboy_options, :certfile) do
fail "missing option :cert/:certfile"
end
end
defp put_ssl_file(cowboy_options, key, value) do
value = to_char_list(value)
unless File.exists?(value) do
fail "the file #{value} required by SSL's #{inspect key} does not exist"
end
Keyword.put(cowboy_options, key, value)
end
defp otp_app(cowboy_options) do
if app = cowboy_options[:otp_app] do
Application.app_dir(app)
else
fail "to use a relative certificate with https, the :otp_app " <>
"option needs to be given to the adapter"
end
end
defp to_char_list(cowboy_options, key) do
if value = cowboy_options[key] do
Keyword.put cowboy_options, key, to_char_list(value)
else
cowboy_options
end
end
defp fail(message) do
raise ArgumentError, message: "could not start Cowboy adapter, " <> message
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
defmodule Plug.Adapters.Cowboy.Conn do
@behaviour Plug.Conn.Adapter
@moduledoc false
alias :cowboy_req, as: Request
def conn(req, transport) do
{path, req} = Request.path req
{host, req} = Request.host req
{port, req} = Request.port req
{meth, req} = Request.method req
{hdrs, req} = Request.headers req
{qs, req} = Request.qs req
{peer, req} = Request.peer req
{remote_ip, _} = peer
%Plug.Conn{
adapter: {__MODULE__, req},
host: host,
method: meth,
owner: self(),
path_info: split_path(path),
peer: peer,
port: port,
remote_ip: remote_ip,
query_string: qs,
req_headers: hdrs,
request_path: path,
scheme: scheme(transport)
}
end
def send_resp(req, status, headers, body) do
{:ok, req} = Request.reply(status, headers, body, req)
{:ok, nil, req}
end
def send_file(req, status, headers, path, offset, length) do
%File.Stat{type: :regular, size: size} = File.stat!(path)
length =
cond do
length == :all -> size
is_integer(length) -> length
end
body_fun = fn(socket, transport) -> transport.sendfile(socket, path, offset, length) end
{:ok, req} = Request.reply(status, headers, Request.set_resp_body_fun(length, body_fun, req))
{:ok, nil, req}
end
def send_chunked(req, status, headers) do
{:ok, req} = Request.chunked_reply(status, headers, req)
{:ok, nil, req}
end
def chunk(req, body) do
Request.chunk(body, req)
end
def read_req_body(req, opts \\ []) do
Request.body(req, opts)
end
def parse_req_multipart(req, opts, callback) do
# We need to remove the length from the list
# otherwise cowboy will attempt to load the
# whole length at once.
{limit, opts} = Keyword.pop(opts, :length, 8_000_000)
{:ok, limit, acc, req} = parse_multipart(Request.part(req), limit, opts, [], callback)
params = Enum.reduce(acc, %{}, &Plug.Conn.Query.decode_pair/2)
if limit > 0 do
{:ok, params, req}
else
{:more, params, req}
end
end
## Helpers
defp scheme(:tcp), do: :http
defp scheme(:ssl), do: :https
defp split_path(path) do
segments = :binary.split(path, "/", [:global])
for segment <- segments, segment != "", do: segment
end
## Multipart
defp parse_multipart({:ok, headers, req}, limit, opts, acc, callback) when limit >= 0 do
case callback.(headers) do
{:binary, name} ->
{:ok, limit, body, req} =
parse_multipart_body(Request.part_body(req, opts), limit, opts, "")
Plug.Conn.Utils.validate_utf8!(body, "multipart body")
parse_multipart(Request.part(req), limit, opts, [{name, body}|acc], callback)
{:file, name, path, %Plug.Upload{} = uploaded} ->
{:ok, file} = File.open(path, [:write, :binary, :delayed_write, :raw])
{:ok, limit, req} =
parse_multipart_file(Request.part_body(req, opts), limit, opts, file)
:ok = File.close(file)
parse_multipart(Request.part(req), limit, opts, [{name, uploaded}|acc], callback)
:skip ->
parse_multipart(Request.part(req), limit, opts, acc, callback)
end
end
defp parse_multipart({:ok, _headers, req}, limit, _opts, acc, _callback) do
{:ok, limit, acc, req}
end
defp parse_multipart({:done, req}, limit, _opts, acc, _callback) do
{:ok, limit, acc, req}
end
defp parse_multipart_body({:more, tail, req}, limit, opts, body) when limit >= 0 do
parse_multipart_body(Request.part_body(req, opts), limit - byte_size(tail), opts, body <> tail)
end
defp parse_multipart_body({:more, _tail, req}, limit, _opts, body) do
{:ok, limit, body, req}
end
defp parse_multipart_body({:ok, tail, req}, limit, _opts, body) when limit >= byte_size(tail) do
{:ok, limit, body <> tail, req}
end
defp parse_multipart_body({:ok, _tail, req}, limit, _opts, body) do
{:ok, limit, body, req}
end
defp parse_multipart_file({:more, tail, req}, limit, opts, file) when limit >= 0 do
IO.binwrite(file, tail)
parse_multipart_file(Request.part_body(req, opts), limit - byte_size(tail), opts, file)
end
defp parse_multipart_file({:more, _tail, req}, limit, _opts, _file) do
{:ok, limit, req}
end
defp parse_multipart_file({:ok, tail, req}, limit, _opts, file) when limit >= byte_size(tail) do
IO.binwrite(file, tail)
{:ok, limit, req}
end
defp parse_multipart_file({:ok, _tail, req}, limit, _opts, _file) do
{:ok, limit, req}
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
defmodule Plug.Adapters.Cowboy.Handler do
@moduledoc false
@connection Plug.Adapters.Cowboy.Conn
@already_sent {:plug_conn, :sent}
def init({transport, :http}, req, {plug, opts}) when transport in [:tcp, :ssl] do
{:upgrade, :protocol, __MODULE__, req, {transport, plug, opts}}
end
def upgrade(req, env, __MODULE__, {transport, plug, opts}) do
conn = @connection.conn(req, transport)
try do
%{adapter: {@connection, req}} =
conn
|> plug.call(opts)
|> maybe_send(plug)
{:ok, req, [{:result, :ok} | env]}
catch
:error, value ->
stack = System.stacktrace()
exception = Exception.normalize(:error, value, stack)
reason = {{exception, stack}, {plug, :call, [conn, opts]}}
terminate(reason, req, stack)
:throw, value ->
stack = System.stacktrace()
reason = {{{:nocatch, value}, stack}, {plug, :call, [conn, opts]}}
terminate(reason, req, stack)
:exit, value ->
stack = System.stacktrace()
reason = {value, {plug, :call, [conn, opts]}}
terminate(reason, req, stack)
after
receive do
@already_sent -> :ok
after
0 -> :ok
end
end
end
defp maybe_send(%Plug.Conn{state: :unset}, _plug), do: raise Plug.Conn.NotSentError
defp maybe_send(%Plug.Conn{state: :set} = conn, _plug), do: Plug.Conn.send_resp(conn)
defp maybe_send(%Plug.Conn{} = conn, _plug), do: conn
defp maybe_send(other, plug) do
raise "Cowboy adapter expected #{inspect plug} to return Plug.Conn but got: #{inspect other}"
end
defp terminate(reason, req, stack) do
:cowboy_req.maybe_reply(stack, req)
exit(reason)
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
defmodule Plug.Adapters.Test.Conn do
@behaviour Plug.Conn.Adapter
@moduledoc false
## Test helpers
def conn(conn, method, uri, body_or_params) do
maybe_flush()
uri = URI.parse(uri)
method = method |> to_string |> String.upcase
query = uri.query || ""
owner = self()
{body, params, req_headers} = body_or_params(body_or_params, query, conn.req_headers)
state = %{method: method, params: params, req_body: body,
chunks: nil, ref: make_ref, owner: owner}
%Plug.Conn{conn |
adapter: {__MODULE__, state},
host: uri.host || "www.example.com",
method: method,
owner: owner,
path_info: split_path(uri.path),
port: uri.port || 80,
peer: {{127, 0, 0, 1}, 111317},
remote_ip: {127, 0, 0, 1},
req_headers: req_headers,
request_path: uri.path,
query_string: query,
params: params || %Plug.Conn.Unfetched{aspect: :params},
scheme: (uri.scheme || "http") |> String.downcase |> String.to_atom}
end
## Connection adapter
def send_resp(%{method: "HEAD"} = state, status, headers, _body) do
do_send state, status, headers, ""
end
def send_resp(state, status, headers, body) do
do_send state, status, headers, IO.iodata_to_binary(body)
end
def send_file(%{method: "HEAD"} = state, status, headers, _path, _offset, _length) do
do_send state, status, headers, ""
end
def send_file(state, status, headers, path, offset, length) do
%File.Stat{type: :regular, size: size} = File.stat!(path)
length =
cond do
length == :all -> size
is_integer(length) -> length
end
{:ok, data} = File.open!(path, [:read, :binary], fn device ->
:file.pread(device, offset, length)
end)
do_send state, status, headers, data
end
def send_chunked(state, _status, _headers),
do: {:ok, "", %{state | chunks: ""}}
def chunk(%{method: "HEAD"} = state, _body),
do: {:ok, "", state}
def chunk(%{chunks: chunks} = state, body) do
body = chunks <> IO.iodata_to_binary(body)
{:ok, body, %{state | chunks: body}}
end
defp do_send(%{owner: owner, ref: ref} = state, status, headers, body) do
send owner, {ref, {status, headers, body}}
{:ok, body, state}
end
def read_req_body(%{req_body: body} = state, opts \\ []) do
size = min(byte_size(body), Keyword.get(opts, :length, 8_000_000))
data = :binary.part(body, 0, size)
rest = :binary.part(body, size, byte_size(body) - size)
tag =
case rest do
"" -> :ok
_ -> :more
end
{tag, data, %{state | req_body: rest}}
end
def parse_req_multipart(%{params: multipart} = state, _limit, _callback) do
{:ok, multipart, %{state | params: nil}}
end
## Private helpers
defp body_or_params(nil, _query, headers),
do: {"", nil, headers}
defp body_or_params(body, _query, headers) when is_binary(body) do
{body, nil, headers}
end
defp body_or_params(params, query, headers) when is_list(params) do
body_or_params(Enum.into(params, %{}), query, headers)
end
defp body_or_params(params, query, headers) when is_map(params) do
headers = :lists.keystore("content-type", 1, headers,
{"content-type", "multipart/mixed; charset: utf-8"})
params = Map.merge(Plug.Conn.Query.decode(query), stringify_params(params))
{"", params, headers}
end
defp stringify_params([{_, _}|_] = params),
do: Enum.into(params, %{}, &stringify_kv/1)
defp stringify_params([_|_] = params),
do: Enum.map(params, &stringify_params/1)
defp stringify_params(%{__struct__: mod} = struct) when is_atom(mod),
do: struct
defp stringify_params(%{} = params),
do: Enum.into(params, %{}, &stringify_kv/1)
defp stringify_params(other),
do: other
defp stringify_kv({k, v}),
do: {to_string(k), stringify_params(v)}
defp split_path(path) do
segments = :binary.split(path, "/", [:global])
for segment <- segments, segment != "", do: segment
end
@already_sent {:plug_conn, :sent}
defp maybe_flush() do
receive do
@already_sent -> :ok
after
0 -> :ok
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
defmodule Plug.Adapters.Translator do
@moduledoc """
A translator module shared by adapters that ship with Plug.
We host all translations in a single module which is added
to Logger when the :plug application starts.
"""
## Entry point
@doc """
The `translate/4` function expected by custom Logger translators.
"""
def translate(min_level, :error, :format,
{'Ranch listener' ++ _, [ref, protocol, pid, reason]}) do
{:ok, translate_ranch(min_level, ref, protocol, pid, reason)}
end
def translate(_min_level, _level, _kind, _data) do
:none
end
## Ranch/Cowboy
defp translate_ranch(min_level, _ref, :cowboy_protocol, pid,
{reason, {mod, :call, [%Plug.Conn{} = conn, _opts]}}) do
[inspect(pid), " running ", inspect(mod), " terminated\n",
conn_info(min_level, conn) |
Exception.format(:exit, reason, [])]
end
defp translate_ranch(_min_level, ref, protocol, pid, reason) do
["Ranch protocol ", inspect(pid), " (", inspect(protocol),
") of listener ", inspect(ref), " terminated\n" |
Exception.format(:exit, reason, [])]
end
## Helpers
defp conn_info(_min_level, conn) do
[server_info(conn), request_info(conn)]
end
defp server_info(%Plug.Conn{host: host, port: port, scheme: scheme}) do
["Server: ", host, ":", Integer.to_string(port), ?\s, ?(, Atom.to_string(scheme), ?), ?\n]
end
defp request_info(%Plug.Conn{method: method, query_string: query_string} = conn) do
["Request: ", method, ?\s, path_to_iodata(conn.request_path, query_string), ?\n]
end
defp path_to_iodata(path, ""), do: path
defp path_to_iodata(path, qs), do: [path, ??, qs]
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
defmodule Plug.Builder do
@moduledoc """
Conveniences for building plugs.
This module can be `use`-d into a module in order to build
a plug pipeline:
defmodule MyApp do
use Plug.Builder
plug Plug.Logger
plug :hello, upper: true
# A function from another module can be plugged too, provided it's
# imported into the current module first.
import AnotherModule, only: [interesting_plug: 2]
plug :interesting_plug
def hello(conn, opts) do
body = if opts[:upper], do: "WORLD", else: "world"
send_resp(conn, 200, body)
end
end
Multiple plugs can be defined with the `plug/2` macro, forming a pipeline.
The plugs in the pipeline will be executed in the order they've been added
through the `plug/2` macro. In the example above, `Plug.Logger` will be
called first and then the `:hello` function plug will be called on the
resulting connection.
`Plug.Builder` also imports the `Plug.Conn` module, making functions like
`send_resp/3` available.
## Options
When used, the following options are accepted by `Plug.Builder`:
* `:log_on_halt` - accepts the level to log whenever the request is halted
## Plug behaviour
Internally, `Plug.Builder` implements the `Plug` behaviour, which means both
the `init/1` and `call/2` functions are defined.
By implementing the Plug API, `Plug.Builder` guarantees this module is a plug
and can be handed to a web server or used as part of another pipeline.
## Overriding the default Plug API functions
Both the `init/1` and `call/2` functions defined by `Plug.Builder` can be
manually overridden. For example, the `init/1` function provided by
`Plug.Builder` returns the options that it receives as an argument, but its
behaviour can be customized:
defmodule PlugWithCustomOptions do
use Plug.Builder
plug Plug.Logger
def init(opts) do
opts
end
end
The `call/2` function that `Plug.Builder` provides is used internally to
execute all the plugs listed using the `plug` macro, so overriding the
`call/2` function generally implies using `super` in order to still call the
plug chain:
defmodule PlugWithCustomCall do
use Plug.Builder
plug Plug.Logger
plug Plug.Head
def call(conn, _opts) do
super(conn, opts) # calls Plug.Logger and Plug.Head
assign(conn, :called_all_plugs, true)
end
end
## Halting a plug pipeline
A plug pipeline can be halted with `Plug.Conn.halt/1`. The builder will
prevent further plugs downstream from being invoked and return the current
connection. In the following example, the `Plug.Logger` plug never gets
called:
defmodule PlugUsingHalt do
use Plug.Builder
plug :stopper
plug Plug.Logger
def stopper(conn, _opts) do
halt(conn)
end
end
"""
@type plug :: module | atom
@doc false
defmacro __using__(opts) do
quote do
@behaviour Plug
@plug_builder_opts unquote(opts)
def init(opts) do
opts
end
def call(conn, opts) do
plug_builder_call(conn, opts)
end
defoverridable [init: 1, call: 2]
import Plug.Conn
import Plug.Builder, only: [plug: 1, plug: 2]
Module.register_attribute(__MODULE__, :plugs, accumulate: true)
@before_compile Plug.Builder
end
end
@doc false
defmacro __before_compile__(env) do
plugs = Module.get_attribute(env.module, :plugs)
builder_opts = Module.get_attribute(env.module, :plug_builder_opts)
if plugs == [] do
raise "no plugs have been defined in #{inspect env.module}"
end
{conn, body} = Plug.Builder.compile(env, plugs, builder_opts)
quote do
defp plug_builder_call(unquote(conn), _), do: unquote(body)
end
end
@doc """
A macro that stores a new plug. `opts` will be passed unchanged to the new
plug.
This macro doesn't add any guards when adding the new plug to the pipeline;
for more information about adding plugs with guards see `compile/1`.
## Examples
plug Plug.Logger # plug module
plug :foo, some_options: true # plug function
"""
defmacro plug(plug, opts \\ []) do
quote do
@plugs {unquote(plug), unquote(opts), true}
end
end
@doc """
Compiles a plug pipeline.
Each element of the plug pipeline (according to the type signature of this
function) has the form:
{plug_name, options, guards}
Note that this function expects a reversed pipeline (with the last plug that
has to be called coming first in the pipeline).
The function returns a tuple with the first element being a quoted reference
to the connection and the second element being the compiled quoted pipeline.
## Examples
Plug.Builder.compile(env, [
{Plug.Logger, [], true}, # no guards, as added by the Plug.Builder.plug/2 macro
{Plug.Head, [], quote(do: a when is_binary(a))}
], [])
"""
@spec compile(Macro.Env.t, [{plug, Plug.opts, Macro.t}], Keyword.t) :: {Macro.t, Macro.t}
def compile(env, pipeline, builder_opts) do
conn = quote do: conn
{conn, Enum.reduce(pipeline, conn, "e_plug(init_plug(&1), &2, env, builder_opts))}
end
# Initializes the options of a plug at compile time.
defp init_plug({plug, opts, guards}) do
case Atom.to_char_list(plug) do
'Elixir.' ++ _ -> init_module_plug(plug, opts, guards)
_ -> init_fun_plug(plug, opts, guards)
end
end
defp init_module_plug(plug, opts, guards) do
initialized_opts = plug.init(opts)
if function_exported?(plug, :call, 2) do
{:module, plug, initialized_opts, guards}
else
raise ArgumentError, message: "#{inspect plug} plug must implement call/2"
end
end
defp init_fun_plug(plug, opts, guards) do
{:function, plug, opts, guards}
end
# `acc` is a series of nested plug calls in the form of
# plug3(plug2(plug1(conn))). `quote_plug` wraps a new plug around that series
# of calls.
defp quote_plug({plug_type, plug, opts, guards}, acc, env, builder_opts) do
call = quote_plug_call(plug_type, plug, opts)
error_message = case plug_type do
:module -> "expected #{inspect plug}.call/2 to return a Plug.Conn"
:function -> "expected #{plug}/2 to return a Plug.Conn"
end <> ", all plugs must receive a connection (conn) and return a connection"
quote do
case unquote(compile_guards(call, guards)) do
%Plug.Conn{halted: true} = conn ->
unquote(log_halt(plug_type, plug, env, builder_opts))
conn
%Plug.Conn{} = conn ->
unquote(acc)
_ ->
raise unquote(error_message)
end
end
end
defp quote_plug_call(:function, plug, opts) do
quote do: unquote(plug)(conn, unquote(Macro.escape(opts)))
end
defp quote_plug_call(:module, plug, opts) do
quote do: unquote(plug).call(conn, unquote(Macro.escape(opts)))
end
defp compile_guards(call, true) do
call
end
defp compile_guards(call, guards) do
quote do
case true do
true when unquote(guards) -> unquote(call)
true -> conn
end
end
end
defp log_halt(plug_type, plug, env, builder_opts) do
if level = builder_opts[:log_on_halt] do
message = case plug_type do
:module -> "#{inspect env.module} halted in #{inspect plug}.call/2"
:function -> "#{inspect env.module} halted in #{inspect plug}/2"
end
quote do
require Logger
Logger.unquote(level)(unquote(message))
end
else
nil
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 |
alias Plug.Conn.Unfetched
defmodule Plug.Conn do
@moduledoc """
The Plug connection.
This module defines a `Plug.Conn` struct and the main functions
for working with Plug connections.
Note request headers are normalized to lowercase and response
headers are expected to have lower-case keys.
## Request fields
These fields contain request information:
* `host` - the requested host as a binary, example: `"www.example.com"`
* `method` - the request method as a binary, example: `"GET"`
* `path_info` - the path split into segments, example: `["hello", "world"]`
* `script_name` - the initial portion of the URL's path that corresponds to the application
routing, as segments, example: ["sub","app"].
* `request_path` - the requested path, example: `/trailing/and//double//slashes/`
* `port` - the requested port as an integer, example: `80`
* `peer` - the actual TCP peer that connected, example: `{{127, 0, 0, 1}, 12345}`. Often this
is not the actual IP and port of the client, but rather of a load-balancer or request-router.
* `remote_ip` - the IP of the client, example: `{151, 236, 219, 228}`. This field is meant to
be overwritten by plugs that understand e.g. the `X-Forwarded-For` header or HAProxy's PROXY
protocol. It defaults to peer's IP.
* `req_headers` - the request headers as a list, example: `[{"content-type", "text/plain"}]`
* `scheme` - the request scheme as an atom, example: `:http`
* `query_string` - the request query string as a binary, example: `"foo=bar"`
## Fetchable fields
The request information in these fields is not populated until it is fetched
using the associated `fetch_` function. For example, the `cookies` field uses
`fetch_cookies/2`.
If you access these fields before fetching them, they will be returned as
`Plug.Conn.Unfetched` structs.
* `cookies`- the request cookies with the response cookies
* `query_params` - the request query params
* `params` - the request params. Usually populated by a plug, like `Plug.Parsers`
* `req_cookies` - the request cookies (without the response ones)
## Response fields
These fields contain response information:
* `resp_body` - the response body, by default is an empty string. It is set
to nil after the response is set, except for test connections.
* `resp_charset` - the response charset, defaults to "utf-8"
* `resp_cookies` - the response cookies with their name and options
* `resp_headers` - the response headers as a dict, by default `cache-control`
is set to `"max-age=0, private, must-revalidate"`
* `status` - the response status
Furthermore, the `before_send` field stores callbacks that are invoked
before the connection is sent. Callbacks are invoked in the reverse order
they are registered (callbacks registered first are invoked last) in order
to reproduce a pipeline ordering.
## Connection fields
* `assigns` - shared user data as a dict
* `owner` - the Elixir process that owns the connection
* `halted` - the boolean status on whether the pipeline was halted
* `secret_key_base` - a secret key used to verify and encrypt cookies.
the field must be set manually whenever one of those features are used.
This data must be kept in the connection and never used directly, always
use `Plug.Crypto.KeyGenerator.generate/3` to derive keys from it
* `state` - the connection state
The connection state is used to track the connection lifecycle. It starts
as `:unset` but is changed to `:set` (via `Plug.Conn.resp/3`) or `:file`
(when invoked via `Plug.Conn.send_file/3`). Its final result is
`:sent` or `:chunked` depending on the response model.
## Private fields
These fields are reserved for libraries/framework usage.
* `adapter` - holds the adapter information in a tuple
* `private` - shared library data as a dict
## Protocols
`Plug.Conn` implements both the Collectable and Inspect protocols
out of the box. The inspect protocol provides a nice representation
of the connection while the collectable protocol allows developers
to easily chunk data. For example:
# Send the chunked response headers
conn = send_chunked(conn, 200)
# Pipe the given list into a connection
# Each item is emitted as a chunk
Enum.into(~w(each chunk as a word), conn)
"""
@type adapter :: {module, term}
@type assigns :: %{atom => any}
@type before_send :: [(t -> t)]
@type body :: iodata | nil
@type cookies :: %{binary => binary}
@type halted :: boolean
@type headers :: [{binary, binary}]
@type host :: binary
@type int_status :: non_neg_integer | nil
@type owner :: pid
@type method :: binary
@type param :: binary | %{binary => param} | [param]
@type params :: %{binary => param}
@type peer :: {:inet.ip_address, :inet.port_number}
@type port_number :: :inet.port_number
@type query_string :: String.t
@type resp_cookies :: %{binary => %{}}
@type scheme :: :http | :https
@type secret_key_base :: binary | nil
@type segments :: [binary]
@type state :: :unset | :set | :file | :chunked | :sent
@type status :: atom | int_status
@type t :: %__MODULE__{
adapter: adapter,
assigns: assigns,
before_send: before_send,
body_params: params | Unfetched.t,
cookies: cookies | Unfetched.t,
host: host,
method: method,
owner: owner,
params: params | Unfetched.t,
path_info: segments,
port: :inet.port_number,
private: assigns,
query_params: params | Unfetched.t,
query_string: query_string,
peer: peer,
remote_ip: :inet.ip_address,
req_cookies: cookies | Unfetched.t,
req_headers: headers,
request_path: binary,
resp_body: body,
resp_cookies: resp_cookies,
resp_headers: headers,
scheme: scheme,
script_name: segments,
secret_key_base: secret_key_base,
state: state,
status: int_status}
defstruct adapter: {Plug.Conn, nil},
assigns: %{},
before_send: [],
body_params: %Unfetched{aspect: :body_params},
cookies: %Unfetched{aspect: :cookies},
halted: false,
host: "www.example.com",
method: "GET",
owner: nil,
params: %Unfetched{aspect: :params},
path_info: [],
port: 0,
private: %{},
query_params: %Unfetched{aspect: :query_params},
query_string: "",
peer: nil,
remote_ip: nil,
req_cookies: %Unfetched{aspect: :cookies},
req_headers: [],
request_path: "",
resp_body: nil,
resp_cookies: %{},
resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}],
scheme: :http,
script_name: [],
secret_key_base: nil,
state: :unset,
status: nil
defmodule NotSentError do
defexception message: "no response was set nor sent from the connection"
@moduledoc """
Error raised when no response is sent in a request
"""
end
defmodule AlreadySentError do
defexception message: "the response was already sent"
@moduledoc """
Error raised when trying to modify or send an already sent response
"""
end
defmodule InvalidHeaderError do
defexception message: "header is invalid"
@moduledoc ~S"""
Error raised when trying to send a header that errors, for example:
* the header key contains uppercase chars
* the header value contains newlines \n
"""
end
alias Plug.Conn
@already_sent {:plug_conn, :sent}
@unsent [:unset, :set]
@doc """
Assigns a value to a key in the connection
## Examples
iex> conn.assigns[:hello]
nil
iex> conn = assign(conn, :hello, :world)
iex> conn.assigns[:hello]
:world
"""
@spec assign(t, atom, term) :: t
def assign(%Conn{assigns: assigns} = conn, key, value) when is_atom(key) do
%{conn | assigns: Map.put(assigns, key, value)}
end
@doc """
Starts a task to assign a value to a key in the connection.
`await_assign/2` can be used to wait for the async task to complete and
retrieve the resulting value.
Behind the scenes, it uses `Task.async/1`.
## Examples
iex> conn.assigns[:hello]
nil
iex> conn = async_assign(conn, :hello, fn -> :world end)
iex> conn.assigns[:hello]
%Task{...}
"""
@spec async_assign(t, atom, (() -> term)) :: t
def async_assign(%Conn{} = conn, key, fun) when is_atom(key) and is_function(fun, 0) do
assign(conn, key, Task.async(fun))
end
@doc """
Awaits the completion of an async assign.
Returns a connection with the value resulting from the async assignment placed
under `key` in the `:assigns` field.
Behind the scenes, it uses `Task.await/2`.
## Examples
iex> conn.assigns[:hello]
nil
iex> conn = async_assign(conn, :hello, fn -> :world end)
iex> conn = await_assign(conn, :hello) # blocks until `conn.assigns[:hello]` is available
iex> conn.assigns[:hello]
:world
"""
@spec await_assign(t, atom, timeout) :: t
def await_assign(%Conn{} = conn, key, timeout \\ 5000) when is_atom(key) do
task = Map.fetch!(conn.assigns, key)
assign(conn, key, Task.await(task, timeout))
end
@doc """
Assigns a new **private** key and value in the connection.
This storage is meant to be used by libraries and frameworks to avoid writing
to the user storage (the `:assigns` field). It is recommended for
libraries/frameworks to prefix the keys with the library name.
For example, if some plug needs to store a `:hello` key, it
should do so as `:plug_hello`:
iex> conn.private[:plug_hello]
nil
iex> conn = put_private(conn, :plug_hello, :world)
iex> conn.private[:plug_hello]
:world
"""
@spec put_private(t, atom, term) :: t
def put_private(%Conn{private: private} = conn, key, value) when is_atom(key) do
%{conn | private: Map.put(private, key, value)}
end
@doc """
Stores the given status code in the connection.
The status code can be `nil`, an integer or an atom. The list of allowed
atoms is available in `Plug.Conn.Status`.
Raises a `Plug.Conn.AlreadySentError` if the connection has already been
`:sent`.
"""
@spec put_status(t, status) :: t
def put_status(%Conn{state: :sent}, _status),
do: raise AlreadySentError
def put_status(%Conn{} = conn, nil),
do: %{conn | status: nil}
def put_status(%Conn{} = conn, status),
do: %{conn | status: Plug.Conn.Status.code(status)}
@doc """
Sends a response to the client.
It expects the connection state to be `:set`, otherwise raises an
`ArgumentError` for `:unset` connections or a `Plug.Conn.AlreadySentError` for
already `:sent` connections.
At the end sets the connection state to `:sent`.
"""
@spec send_resp(t) :: t | no_return
def send_resp(conn)
def send_resp(%Conn{state: :unset}) do
raise ArgumentError, message: "cannot send a response that was not set"
end
def send_resp(%Conn{adapter: {adapter, payload}, state: :set, owner: owner} = conn) do
conn = run_before_send(conn, :set)
{:ok, body, payload} = adapter.send_resp(payload, conn.status, conn.resp_headers, conn.resp_body)
send owner, @already_sent
%{conn | adapter: {adapter, payload}, resp_body: body, state: :sent}
end
def send_resp(%Conn{}) do
raise AlreadySentError
end
@doc """
Sends a file as the response body with the given `status`
and optionally starting at the given offset until the given length.
If available, the file is sent directly over the socket using
the operating system `sendfile` operation.
It expects a connection that has not been `:sent` yet and sets its
state to `:sent` afterwards. Otherwise raises `Plug.Conn.AlreadySentError`.
"""
@spec send_file(t, status, filename :: binary, offset ::integer, length :: integer | :all) :: t | no_return
def send_file(conn, status, file, offset \\ 0, length \\ :all)
def send_file(%Conn{state: state}, status, _file, _offset, _length)
when not state in @unsent do
_ = Plug.Conn.Status.code(status)
raise AlreadySentError
end
def send_file(%Conn{adapter: {adapter, payload}, owner: owner} = conn, status, file, offset, length)
when is_binary(file) do
conn = run_before_send(%{conn | status: Plug.Conn.Status.code(status), resp_body: nil}, :file)
{:ok, body, payload} = adapter.send_file(payload, conn.status, conn.resp_headers, file, offset, length)
send owner, @already_sent
%{conn | adapter: {adapter, payload}, state: :sent, resp_body: body}
end
@doc """
Sends the response headers as a chunked response.
It expects a connection that has not been `:sent` yet and sets its
state to `:chunked` afterwards. Otherwise raises `Plug.Conn.AlreadySentError`.
"""
@spec send_chunked(t, status) :: t | no_return
def send_chunked(%Conn{state: state}, status)
when not state in @unsent do
_ = Plug.Conn.Status.code(status)
raise AlreadySentError
end
def send_chunked(%Conn{adapter: {adapter, payload}, owner: owner} = conn, status) do
conn = run_before_send(%{conn | status: Plug.Conn.Status.code(status), resp_body: nil}, :chunked)
{:ok, body, payload} = adapter.send_chunked(payload, conn.status, conn.resp_headers)
send owner, @already_sent
%{conn | adapter: {adapter, payload}, resp_body: body}
end
@doc """
Sends a chunk as part of a chunked response.
It expects a connection with state `:chunked` as set by
`send_chunked/2`. It returns `{:ok, conn}` in case of success,
otherwise `{:error, reason}`.
"""
@spec chunk(t, body) :: {:ok, t} | {:error, term} | no_return
def chunk(%Conn{adapter: {adapter, payload}, state: :chunked} = conn, chunk) do
case adapter.chunk(payload, chunk) do
:ok -> {:ok, conn}
{:ok, body, payload} -> {:ok, %{conn | resp_body: body, adapter: {adapter, payload}}}
{:error, _} = error -> error
end
end
def chunk(%Conn{}, chunk) when is_binary(chunk) or is_list(chunk) do
raise ArgumentError, message: "chunk/2 expects a chunked response. Please ensure " <>
"you have called send_chunked/2 before you send a chunk"
end
@doc """
Sends a response with the given status and body.
See `send_resp/1` for more information.
"""
@spec send_resp(t, status, body) :: t | no_return
def send_resp(%Conn{} = conn, status, body) do
conn |> resp(status, body) |> send_resp()
end
@doc """
Sets the response to the given `status` and `body`.
It sets the connection state to `:set` (if not already `:set`)
and raises `Plug.Conn.AlreadySentError` if it was already `:sent`.
"""
@spec resp(t, status, body) :: t
def resp(%Conn{state: state}, status, _body)
when not state in @unsent do
_ = Plug.Conn.Status.code(status)
raise AlreadySentError
end
def resp(%Conn{}, _status, nil) do
raise ArgumentError, "response body cannot be set to nil"
end
def resp(%Conn{} = conn, status, body)
when is_binary(body) or is_list(body) do
%{conn | status: Plug.Conn.Status.code(status), resp_body: body, state: :set}
end
@doc """
Returns the values of the request header specified by `key`.
"""
@spec get_req_header(t, binary) :: [binary]
def get_req_header(%Conn{req_headers: headers}, key) when is_binary(key) do
for {k, v} <- headers, k == key, do: v
end
@doc """
Adds a new request header (`key`) if not present, otherwise replaces the
previous value of that header with `value`.
Raises a `Plug.Conn.AlreadySentError` if the connection has already been
`:sent`.
"""
@spec put_req_header(t, binary, binary) :: t
def put_req_header(%Conn{state: :sent}, _key, _value) do
raise AlreadySentError
end
def put_req_header(%Conn{adapter: adapter, req_headers: headers} = conn, key, value) when
is_binary(key) and is_binary(value) do
validate_header_key!(adapter, key)
%{conn | req_headers: List.keystore(headers, key, 0, {key, value})}
end
@doc """
Deletes a request header if present.
Raises a `Plug.Conn.AlreadySentError` if the connection has already been
`:sent`.
"""
@spec delete_req_header(t, binary) :: t
def delete_req_header(%Conn{state: :sent}, _key) do
raise AlreadySentError
end
def delete_req_header(%Conn{req_headers: headers} = conn, key) when
is_binary(key) do
%{conn | req_headers: List.keydelete(headers, key, 0)}
end
@doc """
Updates a request header if present, otherwise it sets it to an initial
value.
Raises a `Plug.Conn.AlreadySentError` if the connection has already been
`:sent`.
"""
@spec update_req_header(t, binary, binary, (binary -> binary)) :: t
def update_req_header(%Conn{state: :sent}, _key, _initial, _fun) do
raise AlreadySentError
end
def update_req_header(%Conn{} = conn, key, initial, fun) when
is_binary(key) and is_binary(initial) and is_function(fun, 1) do
case get_req_header(conn, key) do
[] -> put_req_header(conn, key, initial)
[current|_] -> put_req_header(conn, key, fun.(current))
end
end
@doc """
Returns the values of the response header specified by `key`.
## Examples
iex> conn = %{conn | resp_headers: [{"content-type", "text/plain"}]}
iex> conn |> get_resp_header("content-type")
["text/plain"]
"""
@spec get_resp_header(t, binary) :: [binary]
def get_resp_header(%Conn{resp_headers: headers}, key) when is_binary(key) do
for {k, v} <- headers, k == key, do: v
end
@doc """
Adds a new response header (`key`) if not present, otherwise replaces the
previous value of that header with `value`.
Raises a `Plug.Conn.AlreadySentError` if the connection has already been
`:sent`.
"""
@spec put_resp_header(t, binary, binary) :: t
def put_resp_header(%Conn{state: :sent}, _key, _value) do
raise AlreadySentError
end
def put_resp_header(%Conn{adapter: adapter, resp_headers: headers} = conn, key, value) when
is_binary(key) and is_binary(value) do
validate_header_key!(adapter, key)
validate_header_value!(value)
%{conn | resp_headers: List.keystore(headers, key, 0, {key, value})}
end
@doc """
Merges a series of response headers into the connection.
"""
@spec merge_resp_headers(t, Enum.t) :: t
def merge_resp_headers(%Conn{state: :sent}, _headers) do
raise AlreadySentError
end
def merge_resp_headers(conn, headers) when headers == %{} do
conn
end
def merge_resp_headers(%Conn{resp_headers: current} = conn, headers) do
headers =
Enum.reduce headers, current, fn
{key, value}, acc when is_binary(key) and is_binary(value) ->
List.keystore(acc, key, 0, {key, value})
end
%{conn | resp_headers: headers}
end
@doc """
Deletes a response header if present.
Raises a `Plug.Conn.AlreadySentError` if the connection has already been
`:sent`.
"""
@spec delete_resp_header(t, binary) :: t
def delete_resp_header(%Conn{state: :sent}, _key) do
raise AlreadySentError
end
def delete_resp_header(%Conn{resp_headers: headers} = conn, key) when
is_binary(key) do
%{conn | resp_headers: List.keydelete(headers, key, 0)}
end
@doc """
Updates a response header if present, otherwise it sets it to an initial
value.
Raises a `Plug.Conn.AlreadySentError` if the connection has already been
`:sent`.
"""
@spec update_resp_header(t, binary, binary, (binary -> binary)) :: t
def update_resp_header(%Conn{state: :sent}, _key, _initial, _fun) do
raise AlreadySentError
end
def update_resp_header(%Conn{} = conn, key, initial, fun) when
is_binary(key) and is_binary(initial) and is_function(fun, 1) do
case get_resp_header(conn, key) do
[] -> put_resp_header(conn, key, initial)
[current|_] -> put_resp_header(conn, key, fun.(current))
end
end
@doc """
Sets the value of the `"content-type"` response header taking into account the
`charset`.
"""
@spec put_resp_content_type(t, binary, binary | nil) :: t
def put_resp_content_type(conn, content_type, charset \\ "utf-8")
def put_resp_content_type(conn, content_type, nil) when is_binary(content_type) do
conn |> put_resp_header("content-type", content_type)
end
def put_resp_content_type(conn, content_type, charset) when
is_binary(content_type) and is_binary(charset) do
conn |> put_resp_header("content-type", "#{content_type}; charset=#{charset}")
end
@doc """
Fetches query parameters from the query string.
This function does not fetch parameters from the body. To fetch
parameters from the body, use the `Plug.Parsers` plug.
"""
@spec fetch_query_params(t, Keyword.t) :: t
def fetch_query_params(conn, opts \\ [])
def fetch_query_params(%Conn{query_params: %Unfetched{}, params: params,
query_string: query_string} = conn, _opts) do
Plug.Conn.Utils.validate_utf8!(query_string, "query string")
query_params = Plug.Conn.Query.decode(query_string)
case params do
%Unfetched{} -> %{conn | query_params: query_params, params: query_params}
%{} -> %{conn | query_params: query_params, params: Map.merge(query_params, params)}
end
end
def fetch_query_params(%Conn{} = conn, _opts) do
conn
end
@doc """
Reads the request body.
This function reads a chunk of the request body up to a given `:length`. If
there is more data to be read, then `{:more, partial_body, conn}` is
returned. Otherwise `{:ok, body, conn}` is returned. In case of an error
reading the socket, `{:error, reason}` is returned as per `:gen_tcp.recv/2`.
In order to, for instance, support slower clients you can tune the
`:read_length` and `:read_timeout` options. These specify how much time should
be allowed to pass for each read from the underlying socket.
Because the request body can be of any size, reading the body will only
work once, as Plug will not cache the result of these operations. If you
need to access the body multiple times, it is your responsibility to store
it. Finally keep in mind some plugs like `Plug.Parsers` may read the body,
so the body may be unavailable after accessing such plugs.
This function is able to handle both chunked and identity transfer-encoding
by default.
## Options
* `:length` - sets the maximum number of bytes to read from the body for each
chunk, defaults to 8_000_000 bytes
* `:read_length` - sets the amount of bytes to read at one time from the
underlying socket to fill the chunk, defaults to 1_000_000 bytes
* `:read_timeout` - sets the timeout for each socket read, defaults to
15_000 ms
The values above are not meant to be exact. For example, setting the
length to 8_000_000 may end-up reading some hundred bytes more from
the socket until we halt.
## Examples
{:ok, body, conn} = Plug.Conn.read_body(conn, length: 1_000_000)
"""
@spec read_body(t, Keyword.t) :: {:ok, binary, t} |
{:more, binary, t} |
{:error, term}
def read_body(%Conn{adapter: {adapter, state}} = conn, opts \\ []) do
case adapter.read_req_body(state, opts) do
{:ok, data, state} ->
{:ok, data, %{conn | adapter: {adapter, state}}}
{:more, data, state} ->
{:more, data, %{conn | adapter: {adapter, state}}}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Fetches cookies from the request headers.
"""
@spec fetch_cookies(t, Keyword.t) :: t
def fetch_cookies(conn, opts \\ [])
def fetch_cookies(%Conn{req_cookies: %Unfetched{},
resp_cookies: resp_cookies,
req_headers: req_headers} = conn, _opts) do
req_cookies =
for {"cookie", cookie} <- req_headers,
kv <- Plug.Conn.Cookies.decode(cookie),
into: %{},
do: kv
cookies = Enum.reduce(resp_cookies, req_cookies, fn
{key, opts}, acc ->
if value = Map.get(opts, :value) do
Map.put(acc, key, value)
else
Map.delete(acc, key)
end
end)
%{conn | req_cookies: req_cookies, cookies: cookies}
end
def fetch_cookies(%Conn{} = conn, _opts) do
conn
end
@doc """
Puts a response cookie.
## Options
* `:domain` - the domain the cookie applies to
* `:max_age` - the cookie max-age
* `:path` - the path the cookie applies to
* `:http_only` - when false, the cookie is accessible beyond http
* `:secure` - if the cookie must be sent only over https. Defaults
to true when the connection is https
"""
@spec put_resp_cookie(t, binary, binary, Keyword.t) :: t
def put_resp_cookie(%Conn{resp_cookies: resp_cookies, scheme: scheme} = conn, key, value, opts \\ []) when
is_binary(key) and is_binary(value) and is_list(opts) do
cookie = [{:value, value}|opts] |> :maps.from_list() |> maybe_secure_cookie(scheme)
resp_cookies = Map.put(resp_cookies, key, cookie)
%{conn | resp_cookies: resp_cookies} |> update_cookies(&Map.put(&1, key, value))
end
defp maybe_secure_cookie(cookie, :https), do: Map.put_new(cookie, :secure, true)
defp maybe_secure_cookie(cookie, _), do: cookie
@epoch {{1970, 1, 1}, {0, 0, 0}}
@doc """
Deletes a response cookie.
Deleting a cookie requires the same options as to when the cookie was put.
Check `put_resp_cookie/4` for more information.
"""
@spec delete_resp_cookie(t, binary, Keyword.t) :: t
def delete_resp_cookie(%Conn{resp_cookies: resp_cookies} = conn, key, opts \\ []) when
is_binary(key) and is_list(opts) do
opts = [universal_time: @epoch, max_age: 0] ++ opts
resp_cookies = Map.put(resp_cookies, key, :maps.from_list(opts))
%{conn | resp_cookies: resp_cookies} |> update_cookies(&Map.delete(&1, key))
end
@doc """
Fetches the session from the session store. Will also fetch cookies.
"""
@spec fetch_session(t, Keyword.t) :: t
def fetch_session(conn, opts \\ [])
def fetch_session(%Conn{private: private} = conn, _opts) do
case Map.fetch(private, :plug_session_fetch) do
{:ok, :done} -> conn
{:ok, fun} -> conn |> fetch_cookies |> fun.()
:error -> raise ArgumentError, "cannot fetch session without a configured session plug"
end
end
@doc """
Puts the specified `value` in the session for the given `key`.
The key can be a string or an atom, where atoms are
automatically converted to strings. Can only be invoked
on unsent `conn`s. Will raise otherwise.
"""
@spec put_session(t, String.t | atom, any) :: t
def put_session(%Conn{state: state}, _key, _value) when not state in @unsent,
do: raise AlreadySentError
def put_session(conn, key, value) do
put_session(conn, &Map.put(&1, session_key(key), value))
end
@doc """
Returns session value for the given `key`.
The key can be a string or an atom, where atoms are
automatically converted to strings.
"""
@spec get_session(t, String.t | atom) :: any
def get_session(conn, key) do
conn |> get_session |> Map.get(session_key(key))
end
@doc """
Deletes the session for the given `key`.
The key can be a string or an atom, where atoms are
automatically converted to strings.
"""
@spec delete_session(t, String.t | atom) :: t
def delete_session(%Conn{state: state}, _key) when not state in @unsent,
do: raise AlreadySentError
def delete_session(conn, key) do
put_session(conn, &Map.delete(&1, session_key(key)))
end
@doc """
Clears the entire session.
This function removes every key from the session, clearing the session.
Note that, even if `clear_session/1` is used, the session is still sent to the
client. If the session should be effectively *dropped*, `configure_session/2`
should be used with the `:drop` option set to `true`.
"""
@spec clear_session(t) :: t
def clear_session(conn) do
put_session(conn, fn(_existing) -> Map.new end)
end
@doc """
Configures the session.
## Options
* `:renew` - generates a new session id for the cookie
* `:drop` - drops the session, a session cookie will not be included in the
response
* `:ignore` - ignores all changes made to the session in this request cycle
"""
@spec configure_session(t, Keyword.t) :: t
def configure_session(%Conn{state: state}, _opts) when not state in @unsent,
do: raise AlreadySentError
def configure_session(conn, opts) do
# Ensure the session is available.
_ = get_session(conn)
cond do
opts[:renew] -> put_private(conn, :plug_session_info, :renew)
opts[:drop] -> put_private(conn, :plug_session_info, :drop)
opts[:ignore] -> put_private(conn, :plug_session_info, :ignore)
true -> conn
end
end
@doc """
Registers a callback to be invoked before the response is sent.
Callbacks are invoked in the reverse order they are defined (callbacks
defined first are invoked last).
"""
@spec register_before_send(t, (t -> t)) :: t
def register_before_send(%Conn{state: state}, _callback)
when not state in @unsent do
raise AlreadySentError
end
def register_before_send(%Conn{before_send: before_send} = conn, callback)
when is_function(callback, 1) do
%{conn | before_send: [callback|before_send]}
end
@doc """
Halts the Plug pipeline by preventing further plugs downstream from being
invoked. See the docs for `Plug.Builder` for more information on halting a
plug pipeline.
"""
@spec halt(t) :: t
def halt(%Conn{} = conn) do
%{conn | halted: true}
end
## Helpers
defp run_before_send(%Conn{before_send: before_send} = conn, new) do
conn = Enum.reduce before_send, %{conn | state: new}, &(&1.(&2))
if conn.state != new do
raise ArgumentError, message: "cannot send/change response from run_before_send callback"
end
%{conn | resp_headers: merge_headers(conn.resp_headers, conn.resp_cookies)}
end
defp merge_headers(headers, cookies) do
Enum.reduce(cookies, headers, fn {key, opts}, acc ->
[{"set-cookie", Plug.Conn.Cookies.encode(key, opts)}|acc]
end)
end
defp update_cookies(%Conn{state: :sent}, _fun),
do: raise AlreadySentError
defp update_cookies(%Conn{cookies: %Unfetched{}} = conn, _fun),
do: conn
defp update_cookies(%Conn{cookies: cookies} = conn, fun),
do: %{conn | cookies: fun.(cookies)}
defp session_key(binary) when is_binary(binary), do: binary
defp session_key(atom) when is_atom(atom), do: Atom.to_string(atom)
defp get_session(%Conn{private: private}) do
if session = Map.get(private, :plug_session) do
session
else
raise ArgumentError, message: "session not fetched, call fetch_session/2"
end
end
defp put_session(conn, fun) do
private = conn.private
|> Map.put(:plug_session, get_session(conn) |> fun.())
|> Map.put_new(:plug_session_info, :write)
%{conn | private: private}
end
defp validate_header_key!({Plug.Adapters.Test.Conn, _}, key) do
unless valid_header_key?(key) do
raise InvalidHeaderError, message: "header key is not lowercase: " <> inspect(key)
end
end
defp validate_header_key!(_adapter, _key) do
:ok
end
# Any string containing an UPPERCASE char is not valid.
defp valid_header_key?(<<h, _::binary>>) when h in ?A..?Z, do: false
defp valid_header_key?(<<_, t::binary>>), do: valid_header_key?(t)
defp valid_header_key?(<<>>), do: true
defp valid_header_key?(_), do: false
defp validate_header_value!(value) do
case :binary.match(value, "\n") do
{_, _} -> raise InvalidHeaderError, message: "header value contains newline (\\n): " <> inspect(value)
:nomatch -> :ok
end
end
end
defimpl Inspect, for: Plug.Conn do
def inspect(conn, opts) do
conn =
if opts.limit == :infinity do
conn
else
update_in conn.adapter, fn {adapter, _data} -> {adapter, :...} end
end
Inspect.Any.inspect(conn, opts)
end
end
defimpl Collectable, for: Plug.Conn do
def into(conn) do
{conn, fn
conn, {:cont, x} ->
{:ok, conn} = Plug.Conn.chunk(conn, x)
conn
conn, _ ->
conn
end}
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
defmodule Plug.Conn.Adapter do
@moduledoc """
Specification of the connection adapter API implemented by webservers
"""
use Behaviour
alias Plug.Conn
@typep payload :: term
@doc """
Sends the given status, headers and body as a response
back to the client.
If the request has method `"HEAD"`, the adapter should
not send the response to the client.
Webservers are advised to return `nil` as the sent_body,
as the body can no longer be manipulated. However, the
test implementation returns the actual body so it can
be used during testing.
"""
defcallback send_resp(payload, Conn.status, Conn.headers, Conn.body) ::
{:ok, sent_body :: binary | nil, payload}
@doc """
Sends the given status, headers and file as a response
back to the client.
If the request has method `"HEAD"`, the adapter should
not send the response to the client.
Webservers are advised to return `nil` as the sent_body,
as the body can no longer be manipulated. However, the
test implementation returns the actual body so it can
be used during testing.
"""
defcallback send_file(payload, Conn.status, Conn.headers, file :: binary,
offset :: integer, length :: integer | :all) ::
{:ok, sent_body :: binary | nil, payload}
@doc """
Sends the given status, headers as the beginning of
a chunked response to the client.
Webservers are advised to return `nil` as the sent_body,
as the body can no longer be manipulated. However, the
test implementation returns the actual body so it can
be used during testing.
"""
defcallback send_chunked(payload, Conn.status, Conn.headers) ::
{:ok, sent_body :: binary | nil, payload}
@doc """
Sends a chunk in the chunked response.
If the request has method `"HEAD"`, the adapter should
not send the response to the client.
Webservers are advised to return `:ok` and not modify
any further state for each chunk. However, the test
implementation returns the actual body and payload so
it can be used during testing.
"""
defcallback chunk(payload, Conn.status) ::
:ok | {:ok, sent_body :: binary, payload} | {:error, term}
@doc """
Reads the request body.
Read the docs in `Plug.Conn.read_body/2` for the supported
options and expected behaviour.
"""
defcallback read_req_body(payload, options :: Keyword.t) ::
{:ok, data :: binary, payload} |
{:more, data :: binary, payload} |
{:error, term}
@doc """
Parses a multipart request.
This function receives the payload, the body limit and a callback.
When parsing each multipart segment, the parser should invoke the
given fallback passing the headers for that segment, before consuming
the body. The callback will return one of the following values:
* `{:binary, name}` - the current segment must be treated as a regular
binary value with the given `name`
* `{:file, name, file, upload} - the current segment is a file upload with `name`
and contents should be written to the given `file`
* `:skip` - this multipart segment should be skipped
This function may return a `:ok` or `:more` tuple. The first one is
returned when there is no more multipart data to be processed.
For the supported options, please read `Plug.Conn.read_body/2` docs.
"""
defcallback parse_req_multipart(payload, options :: Keyword.t, fun) ::
{:ok, Conn.params, payload} | {:more, Conn.params, payload}
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
defmodule Plug.Conn.Cookies do
@moduledoc """
Conveniences for encoding and decoding cookies.
"""
@doc """
Decodes the given cookies as given in a request header.
If a cookie is invalid, it is automatically discarded from the result.
## Examples
iex> decode("key1=value1, key2=value2")
%{"key1" => "value1", "key2" => "value2"}
"""
def decode(cookie) do
do_decode(:binary.split(cookie, [";", ","], [:global]), %{})
end
defp do_decode([], acc),
do: acc
defp do_decode([h|t], acc) do
case decode_kv(h) do
{k, v} -> do_decode(t, Map.put(acc, k, v))
false -> do_decode(t, acc)
end
end
defp decode_kv(""),
do: false
defp decode_kv(<< ?$, _ :: binary >>),
do: false
defp decode_kv(<< h, t :: binary >>) when h in [?\s, ?\t],
do: decode_kv(t)
defp decode_kv(kv),
do: decode_key(kv, "")
defp decode_key("", _key),
do: false
defp decode_key(<< ?=, _ :: binary >>, ""),
do: false
defp decode_key(<< ?=, t :: binary >>, key),
do: decode_value(t, "", key, "")
defp decode_key(<< h, _ :: binary >>, _key) when h in [?\s, ?\t, ?\r, ?\n, ?\v, ?\f],
do: false
defp decode_key(<< h, t :: binary >>, key),
do: decode_key(t, << key :: binary, h >>)
defp decode_value("", _spaces, key, value),
do: {key, value}
defp decode_value(<< ?\s, t :: binary >>, spaces, key, value),
do: decode_value(t, << spaces :: binary, ?\s >>, key, value)
defp decode_value(<< h, _ :: binary >>, _spaces, _key, _value) when h in [?\t, ?\r, ?\n, ?\v, ?\f],
do: false
defp decode_value(<< h, t :: binary >>, spaces, key, value),
do: decode_value(t, "", key, << value :: binary, spaces :: binary , h >>)
@doc """
Encodes the given cookies as expected in a response header.
"""
def encode(key, opts \\ %{}) when is_map(opts) do
value = Map.get(opts, :value)
path = Map.get(opts, :path, "/")
header = "#{key}=#{value}; path=#{path}"
if domain = Map.get(opts, :domain) do
header = header <> "; domain=#{domain}"
end
if max_age = Map.get(opts, :max_age) do
time = Map.get(opts, :universal_time) || :calendar.universal_time
time = add_seconds(time, max_age)
header = header <> "; expires=" <> rfc2822(time) <> "; max-age=" <> Integer.to_string(max_age)
end
if Map.get(opts, :secure, false) do
header = header <> "; secure"
end
if Map.get(opts, :http_only, true) do
header = header <> "; HttpOnly"
end
header
end
defp pad(number) when number in 0..9, do: <<?0, ?0 + number>>
defp pad(number), do: Integer.to_string(number)
defp rfc2822({{year, month, day} = date, {hour, minute, second}}) do
weekday_name = weekday_name(:calendar.day_of_the_week(date))
month_name = month_name(month)
padded_day = pad(day)
padded_hour = pad(hour)
padded_minute = pad(minute)
padded_second = pad(second)
binary_year = Integer.to_string(year)
weekday_name <> ", " <> padded_day <>
" " <> month_name <> " " <> binary_year <>
" " <> padded_hour <> ":" <> padded_minute <>
":" <> padded_second <> " GMT"
end
defp weekday_name(1), do: "Mon"
defp weekday_name(2), do: "Tue"
defp weekday_name(3), do: "Wed"
defp weekday_name(4), do: "Thu"
defp weekday_name(5), do: "Fri"
defp weekday_name(6), do: "Sat"
defp weekday_name(7), do: "Sun"
defp month_name(1), do: "Jan"
defp month_name(2), do: "Feb"
defp month_name(3), do: "Mar"
defp month_name(4), do: "Apr"
defp month_name(5), do: "May"
defp month_name(6), do: "Jun"
defp month_name(7), do: "Jul"
defp month_name(8), do: "Aug"
defp month_name(9), do: "Sep"
defp month_name(10), do: "Oct"
defp month_name(11), do: "Nov"
defp month_name(12), do: "Dec"
defp add_seconds(time, seconds_to_add) do
time_seconds = :calendar.datetime_to_gregorian_seconds(time)
:calendar.gregorian_seconds_to_datetime(time_seconds + seconds_to_add)
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
defmodule Plug.Conn.Query do
@moduledoc """
Conveniences for decoding and encoding url encoded queries.
Plug allows a developer to build query strings
that map to Elixir structures in order to make
manipulation of such structures easier on the server
side. Here are some examples:
iex> decode("foo=bar")["foo"]
"bar"
If a value is given more than once, the last value takes precedence:
iex> decode("foo=bar&foo=baz")["foo"]
"baz"
Nested structures can be created via `[key]`:
iex> decode("foo[bar]=baz")["foo"]["bar"]
"baz"
Lists are created with `[]`:
iex> decode("foo[]=bar&foo[]=baz")["foo"]
["bar", "baz"]
Dicts can be encoded:
iex> encode(%{foo: "bar", baz: "bat"})
"baz=bat&foo=bar"
Encoding keyword lists preserves the order of the fields:
iex> encode([foo: "bar", baz: "bat"])
"foo=bar&baz=bat"
When encoding keyword lists with duplicate keys, the key that comes first
takes precedence:
iex> encode([foo: "bar", foo: "bat"])
"foo=bar"
Encoding named lists:
iex> encode(%{foo: ["bar", "baz"]})
"foo[]=bar&foo[]=baz"
Encoding nested structures:
iex> encode(%{foo: %{bar: "baz"}})
"foo[bar]=baz"
"""
@doc """
Decodes the given binary.
"""
def decode(query, initial \\ %{})
def decode("", initial) do
initial
end
def decode(query, initial) do
parts = :binary.split(query, "&", [:global])
Enum.reduce(Enum.reverse(parts), initial, &decode_string_pair(&1, &2))
end
defp decode_string_pair(binary, acc) do
current =
case :binary.split(binary, "=") do
[key, value] ->
{URI.decode_www_form(key), URI.decode_www_form(value)}
[key] ->
{URI.decode_www_form(key), nil}
end
decode_pair(current, acc)
end
@doc """
Decodes the given tuple and stores it in the accumulator.
It parses the key and stores the value into the current
accumulator.
Parameter lists are added to the accumulator in reverse
order, so be sure to pass the parameters in reverse order.
"""
def decode_pair({key, value}, acc) do
parts =
if key != "" and :binary.last(key) == ?] do
# Remove trailing ]
subkey = :binary.part(key, 0, byte_size(key) - 1)
# Split the first [ then split remaining ][.
#
# users[address][street #=> [ "users", "address][street" ]
#
case :binary.split(subkey, "[") do
[key, subpart] ->
[key|:binary.split(subpart, "][", [:global])]
_ ->
[key]
end
else
[key]
end
assign_parts parts, value, acc
end
# We always assign the value in the last segment.
# `age=17` would match here.
defp assign_parts([key], value, acc) do
Map.put_new(acc, key, value)
end
# The current segment is a list. We simply prepend
# the item to the list or create a new one if it does
# not yet. This assumes that items are iterated in
# reverse order.
defp assign_parts([key,""|t], value, acc) do
case Map.fetch(acc, key) do
{:ok, current} when is_list(current) ->
Map.put(acc, key, assign_list(t, current, value))
:error ->
Map.put(acc, key, assign_list(t, [], value))
_ ->
acc
end
end
# The current segment is a parent segment of a
# map. We need to create a map and then
# continue looping.
defp assign_parts([key|t], value, acc) do
case Map.fetch(acc, key) do
{:ok, %{} = current} ->
Map.put(acc, key, assign_parts(t, value, current))
:error ->
Map.put(acc, key, assign_parts(t, value, %{}))
_ ->
acc
end
end
defp assign_list(t, current, value) do
if value = assign_list(t, value), do: [value|current], else: current
end
defp assign_list([], value), do: value
defp assign_list(t, value), do: assign_parts(t, value, %{})
@doc """
Encodes the given dict.
"""
def encode(dict, encoder \\ &to_string/1) do
IO.iodata_to_binary encode_pair("", dict, encoder)
end
# covers structs
defp encode_pair(field, %{__struct__: struct} = map, encoder) when is_atom(struct) do
[field, ?=|encode_value(map, encoder)]
end
# covers maps
defp encode_pair(parent_field, %{} = map, encoder) do
encode_dict(map, parent_field, encoder)
end
# covers keyword lists
defp encode_pair(parent_field, list, encoder) when is_list(list) and is_tuple(hd(list)) do
encode_dict(Enum.uniq(list, &elem(&1, 0)), parent_field, encoder)
end
# covers non-keyword lists
defp encode_pair(parent_field, list, encoder) when is_list(list) do
prune Enum.flat_map list, fn value ->
[?&, encode_pair(parent_field <> "[]", value, encoder)]
end
end
# covers nil
defp encode_pair(field, nil, _encoder) do
[field, ?=]
end
# encoder fallback
defp encode_pair(field, value, encoder) do
[field, ?=|encode_value(value, encoder)]
end
defp encode_dict(dict, parent_field, encoder) do
prune Enum.flat_map dict, fn
{_, value} when value in [%{}, []] ->
[]
{field, value} ->
field =
if parent_field == "" do
encode_key(field)
else
parent_field <> "[" <> encode_key(field) <> "]"
end
[?&, encode_pair(field, value, encoder)]
end
end
defp encode_key(item) do
item |> to_string |> URI.encode_www_form
end
defp encode_value(item, encoder) do
item |> encoder.() |> URI.encode_www_form
end
defp prune([?&|t]), do: t
defp prune([]), do: []
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
defmodule Plug.Conn.Status do
@moduledoc """
Conveniences for working with status codes.
"""
statuses = [
continue: 100,
switching_protocols: 101,
processing: 102,
ok: 200,
created: 201,
accepted: 202,
non_authoritative_information: 203,
no_content: 204,
reset_content: 205,
partial_content: 206,
multi_status: 207,
already_reported: 208,
instance_manipulation_used: 226,
multiple_choices: 300,
moved_permanently: 301,
found: 302,
see_other: 303,
not_modified: 304,
use_proxy: 305,
reserved: 306,
temporary_redirect: 307,
permanent_redirect: 308,
bad_request: 400,
unauthorized: 401,
payment_required: 402,
forbidden: 403,
not_found: 404,
method_not_allowed: 405,
not_acceptable: 406,
proxy_authentication_required: 407,
request_timeout: 408,
conflict: 409,
gone: 410,
length_required: 411,
precondition_failed: 412,
request_entity_too_large: 413,
request_uri_too_long: 414,
unsupported_media_type: 415,
requested_range_not_satisfiable: 416,
expectation_failed: 417,
im_a_teapot: 418,
misdirected_request: 421,
unprocessable_entity: 422,
locked: 423,
failed_dependency: 424,
upgrade_required: 426,
precondition_required: 428,
too_many_requests: 429,
request_header_fields_too_large: 431,
internal_server_error: 500,
not_implemented: 501,
bad_gateway: 502,
service_unavailable: 503,
gateway_timeout: 504,
http_version_not_supported: 505,
variant_also_negotiates: 506,
insufficient_storage: 507,
loop_detected: 508,
not_extended: 510,
network_authentication_required: 511
]
doc = Enum.map(statuses, fn {atom, code} ->
" * `#{inspect atom}` - #{code}\n"
end)
@doc """
Returns the status code given an integer or a known atom.
## Known status codes
The following status codes can be given as atoms with their
respective value shown next:
#{doc}
"""
@spec code(integer | atom) :: integer
def code(integer_or_atom)
def code(integer) when integer in 100..999 do
integer
end
for {atom, code} <- statuses do
def code(unquote(atom)), do: unquote(code)
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
defmodule Plug.Conn.Unfetched do
@moduledoc """
A struct used as default on unfetched fields.
The `:aspect` key of the struct specifies what field is still unfetched.
## Examples
unfetched = %Plug.Conn.Unfetched{aspect: :cookies}
"""
defstruct [:aspect]
@type t :: %__MODULE__{aspect: atom()}
@doc false
def get(%{aspect: aspect}, key, _value) do
raise ArgumentError, "cannot get key #{inspect key} from conn.#{aspect} because they were not fetched"
end
@doc false
def get_and_update(%{aspect: aspect}, key, _fun) do
raise ArgumentError, "cannot get_and_update key #{inspect key} from conn.#{aspect} because they were not fetched"
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
defmodule Plug.Conn.Utils do
@moduledoc """
Utilities for working with connection data
"""
@type params :: [{binary, binary}]
@upper ?A..?Z
@lower ?a..?z
@alpha ?0..?9
@other [?., ?-, ?+]
@space [?\s, ?\t]
@specials ~c|()<>@,;:\\"/[]?={}|
@doc ~S"""
Parses media types (with wildcards).
Type and subtype are case insensitive while the
sensitiveness of params depends on their keys and
therefore are not handled by this parser.
Returns:
* `{:ok, type, subtype, map_of_params}` if everything goes fine
* `:error` if the media type isn't valid
## Examples
iex> media_type "text/plain"
{:ok, "text", "plain", %{}}
iex> media_type "APPLICATION/vnd.ms-data+XML"
{:ok, "application", "vnd.ms-data+xml", %{}}
iex> media_type "text/*; q=1.0"
{:ok, "text", "*", %{"q" => "1.0"}}
iex> media_type "*/*; q=1.0"
{:ok, "*", "*", %{"q" => "1.0"}}
iex> media_type "x y"
:error
iex> media_type "*/html"
:error
iex> media_type "/"
:error
iex> media_type "x/y z"
:error
"""
@spec media_type(binary) :: {:ok, type :: binary, subtype :: binary, params} | :error
def media_type(binary) do
case strip_spaces(binary) do
"*/*" <> t -> mt_params(t, "*", "*")
t -> mt_first(t, "")
end
end
defp mt_first(<<?/, t :: binary>>, acc) when acc != "",
do: mt_wildcard(t, acc)
defp mt_first(<<h, t :: binary>>, acc) when h in @upper,
do: mt_first(t, <<acc :: binary, downcase_char(h)>>)
defp mt_first(<<h, t :: binary>>, acc) when h in @lower or h in @alpha or h == ?-,
do: mt_first(t, <<acc :: binary, h>>)
defp mt_first(_, _acc),
do: :error
defp mt_wildcard(<<?*, t :: binary>>, first),
do: mt_params(t, first, "*")
defp mt_wildcard(t, first),
do: mt_second(t, "", first)
defp mt_second(<<h, t :: binary>>, acc, first) when h in @upper,
do: mt_second(t, <<acc :: binary, downcase_char(h)>>, first)
defp mt_second(<<h, t :: binary>>, acc, first) when h in @lower or h in @alpha or h in @other,
do: mt_second(t, <<acc :: binary, h>>, first)
defp mt_second(t, acc, first),
do: mt_params(t, first, acc)
defp mt_params(t, first, second) do
case strip_spaces(t) do
"" -> {:ok, first, second, %{}}
";" <> t -> {:ok, first, second, params(t)}
_ -> :error
end
end
@doc ~S"""
Parses content type (without wildcards).
It is similar to `media_type/1` except wildcards are
not accepted in the type nor in the subtype.
## Examples
iex> content_type "x-sample/json; charset=utf-8"
{:ok, "x-sample", "json", %{"charset" => "utf-8"}}
iex> content_type "x-sample/json ; charset=utf-8 ; foo=bar"
{:ok, "x-sample", "json", %{"charset" => "utf-8", "foo" => "bar"}}
iex> content_type "\r\n text/plain;\r\n charset=utf-8\r\n"
{:ok, "text", "plain", %{"charset" => "utf-8"}}
iex> content_type "text/plain"
{:ok, "text", "plain", %{}}
iex> content_type "x/*"
:error
iex> content_type "*/*"
:error
"""
@spec content_type(binary) :: {:ok, type :: binary, subtype :: binary, params} | :error
def content_type(binary) do
case media_type(binary) do
{:ok, _, "*", _} -> :error
{:ok, _, _, _} = ok -> ok
:error -> :error
end
end
@doc """
Parses headers parameters.
Keys are case insensitive and downcased,
invalid key-value pairs are discarded.
## Examples
iex> params("foo=bar")
%{"foo" => "bar"}
iex> params(" foo=bar ")
%{"foo" => "bar"}
iex> params("FOO=bar")
%{"foo" => "bar"}
iex> params("Foo=bar; baz=BOING")
%{"foo" => "bar", "baz" => "BOING"}
iex> params("foo=BAR ; wat")
%{"foo" => "BAR"}
iex> params("=")
%{}
"""
@spec params(binary) :: params
def params(t) do
t
|> :binary.split(";", [:global])
|> Enum.reduce(%{}, ¶ms/2)
end
defp params(param, acc) do
case params_key(strip_spaces(param), "") do
{k, v} -> Map.put(acc, k, v)
false -> acc
end
end
defp params_key(<<?=, t :: binary>>, acc) when acc != "",
do: params_value(t, acc)
defp params_key(<<h, _ :: binary>>, _acc) when h in @specials or h in @space or h < 32 or h === 127,
do: false
defp params_key(<<h, t :: binary>>, acc),
do: params_key(t, <<acc :: binary, downcase_char(h)>>)
defp params_key(<<>>, _acc),
do: false
defp params_value(token, key) do
case token(token) do
false -> false
value -> {key, value}
end
end
@doc ~S"""
Parses a value as defined in [RFC-1341][1].
For convenience, trims whitespace at the end of the token.
Returns `false` if the token is invalid.
[1]: http://www.w3.org/Protocols/rfc1341/4_Content-Type.html
## Examples
iex> token("foo")
"foo"
iex> token("foo-bar")
"foo-bar"
iex> token("<foo>")
false
iex> token(~s["<foo>"])
"<foo>"
iex> token(~S["<f\oo>\"<b\ar>"])
"<foo>\"<bar>"
iex> token("foo ")
"foo"
iex> token("foo bar")
false
"""
@spec token(binary) :: binary | false
def token(""),
do: false
def token(<<?", quoted :: binary>>),
do: quoted_token(quoted, "")
def token(token),
do: unquoted_token(token, "")
defp quoted_token(<<>>, _acc),
do: false
defp quoted_token(<<?", t :: binary>>, acc),
do: strip_spaces(t) == "" and acc
defp quoted_token(<<?\\, h, t :: binary>>, acc),
do: quoted_token(t, <<acc :: binary, h>>)
defp quoted_token(<<h, t :: binary>>, acc),
do: quoted_token(t, <<acc :: binary, h>>)
defp unquoted_token(<<>>, acc),
do: acc
defp unquoted_token("\r\n" <> t, acc),
do: strip_spaces(t) == "" and acc
defp unquoted_token(<<h, t :: binary>>, acc) when h in @space,
do: strip_spaces(t) == "" and acc
defp unquoted_token(<<h, _ :: binary>>, _acc) when h in @specials or h < 32 or h === 127,
do: false
defp unquoted_token(<<h, t :: binary>>, acc),
do: unquoted_token(t, <<acc :: binary, h>>)
@doc """
Parses a comma-separated list of header values.
## Examples
iex> list("foo, bar")
["foo", "bar"]
iex> list("foobar")
["foobar"]
iex> list("")
[]
iex> list("empties, , are,, filtered")
["empties", "are", "filtered"]
"""
@spec list(binary) :: [binary]
def list(binary) do
for elem <- :binary.split(binary, ",", [:global]),
(stripped = strip_spaces(elem)) != "",
do: stripped
end
@doc """
Validates the given binary is valid UTF-8.
"""
@spec validate_utf8!(binary, binary) :: :ok | no_return
def validate_utf8!(<<_ :: utf8, t :: binary>>, context) do
validate_utf8!(t, context)
end
def validate_utf8!(<<h, _ :: binary>>, context) do
raise Plug.Parsers.BadEncodingError,
message: "invalid UTF-8 on #{context}, got byte #{h}"
end
def validate_utf8!(<<>>, _context) do
:ok
end
## Helpers
defp strip_spaces("\r\n" <> t),
do: strip_spaces(t)
defp strip_spaces(<<h, t :: binary>>) when h in [?\s, ?\t],
do: strip_spaces(t)
defp strip_spaces(t),
do: t
defp downcase_char(char) when char in @upper, do: char + 32
defp downcase_char(char), do: char
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
defmodule Plug.Conn.WrapperError do
@moduledoc """
Wraps the connection in an error which is meant
to be handled upper in the stack.
Used by both `Plug.Debugger` and `Plug.ErrorHandler`.
"""
defexception [:conn, :kind, :reason, :stack]
def message(%{kind: kind, reason: reason, stack: stack}) do
Exception.format_banner(kind, reason, stack)
end
@doc """
Reraises an error or a wrapped one.
"""
def reraise(_conn, :error, %__MODULE__{stack: stack} = reason) do
:erlang.raise(:error, reason, stack)
end
def reraise(conn, kind, reason) do
stack = System.stacktrace
wrapper = %__MODULE__{conn: conn, kind: kind, reason: reason, stack: stack}
:erlang.raise(:error, wrapper, stack)
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
defmodule Plug.Crypto do
@moduledoc """
Namespace and module for crypto functionality.
"""
use Bitwise
@doc """
Masks the token on the left with the token on the right.
Both tokens are required to have the same size.
"""
def mask(left, right) do
mask(left, right, "")
end
defp mask(<<x, left::binary>>, <<y, right::binary>>, acc) do
mask(left, right, <<acc::binary, x ^^^ y>>)
end
defp mask(<<>>, <<>>, acc) do
acc
end
@doc """
Compares the two binaries (one being masked) in constant-time to avoid
timing attacks.
It is assumed the right token is masked according to the given mask.
"""
def masked_compare(left, right, mask) do
if byte_size(left) == byte_size(right) do
masked_compare(left, right, mask, 0) == 0
else
false
end
end
defp masked_compare(<<x, left::binary>>, <<y, right::binary>>, <<z, mask::binary>>, acc) do
masked_compare(left, right, mask, acc ||| (x ^^^ (y ^^^ z)))
end
defp masked_compare(<<>>, <<>>, <<>>, acc) do
acc
end
@doc """
Compares the two binaries in constant-time to avoid timing attacks.
See: http://codahale.com/a-lesson-in-timing-attacks/
"""
def secure_compare(left, right) do
if byte_size(left) == byte_size(right) do
secure_compare(left, right, 0) == 0
else
false
end
end
defp secure_compare(<<x, left :: binary>>, <<y, right :: binary>>, acc) do
secure_compare(left, right, acc ||| (x ^^^ y))
end
defp secure_compare(<<>>, <<>>, acc) do
acc
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
defmodule Plug.Crypto.KeyGenerator do
@moduledoc """
`KeyGenerator` implements PBKDF2 (Password-Based Key Derivation Function 2),
part of PKCS #5 v2.0 (Password-Based Cryptography Specification).
It can be used to derive a number of keys for various purposes from a given
secret. This lets applications have a single secure secret, but avoid reusing
that key in multiple incompatible contexts.
see http://tools.ietf.org/html/rfc2898#section-5.2
"""
use Bitwise
@max_length bsl(1, 32) - 1
@doc """
Returns a derived key suitable for use.
## Options
* `:iterations` - defaults to 1000 (increase to at least 2^16 if used for passwords);
* `:length` - a length in octets for the derived key. Defaults to 32;
* `:digest` - an hmac function to use as the pseudo-random function. Defaults to `:sha256`;
* `:cache` - an ETS table name to be used as cache.
Only use an ETS table as cache if the secret and salt is a bound set of values.
For example: `:ets.new(:your_name, [:named_table, :public, read_concurrency: true])`
"""
def generate(secret, salt, opts \\ []) do
iterations = Keyword.get(opts, :iterations, 1000)
length = Keyword.get(opts, :length, 32)
digest = Keyword.get(opts, :digest, :sha256)
cache = Keyword.get(opts, :cache)
if length > @max_length do
raise ArgumentError, "length must be less than or equal to #{@max_length}"
else
with_cache cache, {secret, salt, iterations, length, digest}, fn ->
generate(mac_fun(digest, secret), salt, iterations, length, 1, [], 0)
end
end
end
defp with_cache(nil, _key, fun), do: fun.()
defp with_cache(ets, key, fun) do
case :ets.lookup(ets, key) do
[{_key, value}] ->
value
[] ->
value = fun.()
:ets.insert(ets, [{key, value}])
value
end
end
defp generate(_fun, _salt, _iterations, max_length, _block_index, acc, length)
when length >= max_length do
key = acc |> Enum.reverse |> IO.iodata_to_binary
<<bin::binary-size(max_length), _::binary>> = key
bin
end
defp generate(fun, salt, iterations, max_length, block_index, acc, length) do
initial = fun.(<<salt::binary, block_index::integer-size(32)>>)
block = iterate(fun, iterations - 1, initial, initial)
generate(fun, salt, iterations, max_length, block_index + 1,
[block | acc], byte_size(block) + length)
end
defp iterate(_fun, 0, _prev, acc), do: acc
defp iterate(fun, iteration, prev, acc) do
next = fun.(prev)
iterate(fun, iteration - 1, next, :crypto.exor(next, acc))
end
defp mac_fun(digest, secret) do
&:crypto.hmac(digest, secret, &1)
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
defmodule Plug.Crypto.MessageEncryptor do
@moduledoc ~S"""
`MessageEncryptor` is a simple way to encrypt values which get stored
somewhere you don't trust.
The cipher text and initialization vector are base64 encoded and
returned to you.
This can be used in situations similar to the `MessageVerifier`, but where
you don't want users to be able to determine the value of the payload.
## Example
secret_key_base = "072d1e0157c008193fe48a670cce031faa4e..."
encrypted_cookie_salt = "encrypted cookie"
encrypted_signed_cookie_salt = "signed encrypted cookie"
secret = KeyGenerator.generate(secret_key_base, encrypted_cookie_salt)
sign_secret = KeyGenerator.generate(secret_key_base, encrypted_signed_cookie_salt)
data = "José"
encrypted = MessageEncryptor.encrypt_and_sign(data, secret, sign_secret)
decrypted = MessageEncryptor.verify_and_decrypt(encrypted, secret, sign_secret)
decrypted # => "José"
"""
alias Plug.Crypto.MessageVerifier
@doc """
Encrypts and signs a message.
"""
def encrypt_and_sign(message, secret, sign_secret, cipher \\ :aes_cbc256)
when is_binary(message) and is_binary(secret) and is_binary(sign_secret) do
iv = :crypto.strong_rand_bytes(16)
message
|> pad_message
|> encrypt(cipher, secret, iv)
|> Base.encode64()
|> Kernel.<>("--#{Base.encode64(iv)}")
|> MessageVerifier.sign(sign_secret)
end
@doc """
Decrypts and verifies a message.
We need to verify the message in order to avoid padding attacks.
Reference: http://www.limited-entropy.com/padding-oracle-attacks
"""
def verify_and_decrypt(encrypted, secret, sign_secret, cipher \\ :aes_cbc256)
when is_binary(encrypted) and is_binary(secret) and is_binary(sign_secret) do
case MessageVerifier.verify(encrypted, sign_secret) do
{:ok, verified} ->
[encrypted, iv] = String.split(verified, "--")
case Base.decode64(encrypted) do
{:ok, encrypted} ->
case Base.decode64(iv) do
{:ok, iv} ->
encrypted |> decrypt(cipher, secret, iv) |> unpad_message
:error ->
:error
end
:error ->
:error
end
:error ->
:error
end
end
defp encrypt(message, cipher, secret, iv) do
:crypto.block_encrypt(cipher, trim_secret(secret), iv, message)
end
defp decrypt(encrypted, cipher, secret, iv) do
:crypto.block_decrypt(cipher, trim_secret(secret), iv, encrypted)
end
defp pad_message(msg) do
bytes_remaining = rem(byte_size(msg), 16)
padding_size = 16 - bytes_remaining
msg <> :binary.copy(<<padding_size>>, padding_size)
end
defp unpad_message(msg) do
padding_size = :binary.last(msg)
if padding_size <= 16 do
msg_size = byte_size(msg)
if binary_part(msg, msg_size, -padding_size) == :binary.copy(<<padding_size>>, padding_size) do
{:ok, binary_part(msg, 0, msg_size - padding_size)}
else
:error
end
else
:error
end
end
defp trim_secret(secret) do
case byte_size(secret) do
large when large > 32 -> :binary.part(secret, 0, 32)
_ -> secret
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
defmodule Plug.Crypto.MessageVerifier do
@moduledoc """
`MessageVerifier` makes it easy to generate and verify messages
which are signed to prevent tampering.
For example, the cookie store uses this verifier to send data
to the client. The data can be read by the client, but cannot be
tampered with.
"""
@doc """
Decodes and verifies the encoded binary was not tampared with.
"""
def verify(binary, secret) when is_binary(binary) and is_binary(secret) do
case String.split(binary, "--") do
[content, digest] when content != "" and digest != "" ->
if Plug.Crypto.secure_compare(digest(secret, content), digest) do
Base.decode64(content)
else
:error
end
_ ->
:error
end
end
@doc """
Signs a binary according to the given secret.
"""
def sign(binary, secret) when is_binary(binary) and is_binary(secret) do
encoded = Base.encode64(binary)
encoded <> "--" <> digest(secret, encoded)
end
defp digest(secret, data) do
:crypto.hmac(:sha, secret, data) |> Base.encode64
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
defmodule Plug.CSRFProtection do
@moduledoc """
Plug to protect from cross-site request forgery.
For this plug to work, it expects a session to have been
previously fetched. It will then compare the plug stored
in the session with the one sent by the request to determine
the validity of the request. For an invaid request the action
taken is based on the `:with` option.
The token may be sent by the request either via the params
with key "_csrf_token" or a header with name "x-csrf-token".
GET requests are not protected, as they should not have any
side-effect or change your application state. JavaScript
requests are an exception: by using a script tag, external
websites can embed server-side generated JavaScript, which
can leak information. For this reason, this plug also forbids
any GET JavaScript request that is not XHR (or AJAX).
## Token generation
This plug won't generate tokens automatically. Instead,
tokens will be generated only when required by calling
`Plug.CSRFProtection.get_csrf_token/0`. The token is then
stored in the process dictionary to be set in the request.
One may wonder: why the process dictionary?
The CSRF token is usually generated inside forms which may
be isolated from the connection. Storing them in the process
dictionary allows them to be generated as a side-effect,
becoming one of those rare situations where using the process
dictionary is useful.
## Options
* `:with` - should be one of `:exception` or `:clear_session`. Defaults to
`:exception`.
* `:exception` - for invalid requests, this plug will raise
`Plug.CSRFProtection.InvalidCSRFTokenError`.
* `:clear_session` - for invalid requests, this plug will set an empty
session for only this request. Also any changes to the session during this
request will be ignored.
## Disabling
You may disable this plug by doing
`Plug.Conn.put_private(:plug_skip_csrf_protection, true)`. This was made
available for disabling `Plug.CSRFProtection` in tests and not for dynamically
skipping `Plug.CSRFProtection` in production code. If you want specific routes to
skip `Plug.CSRFProtection`, then use a different stack of plugs for that route that
does not include `Plug.CSRFProtection`.
## Examples
plug Plug.Session, ...
plug :fetch_session
plug Plug.CSRFProtection
"""
import Plug.Conn
require Bitwise
@unprotected_methods ~w(HEAD GET OPTIONS)
defmodule InvalidCSRFTokenError do
@moduledoc "Error raised when CSRF token is invalid."
message =
"invalid CSRF (Cross Site Forgery Protection) token, make sure all "
<> "requests include a '_csrf_token' param or an 'x-csrf-token' header"
defexception message: message, plug_status: 403
end
defmodule InvalidCrossOriginRequestError do
@moduledoc "Error raised when non-XHR requests are used for Javascript responses."
message =
"security warning: an embedded <script> tag on another site requested "
<> "protected JavaScript (if you know what you're doing, disable forgery "
<> "protection for this route)"
defexception message: message, plug_status: 403
end
## API
@doc """
Gets the CSRF token.
Generates a token and stores it in the process
dictionary if one does not exists.
"""
def get_csrf_token do
if token = Process.get(:plug_masked_csrf_token) do
token
else
token = mask(unmasked_csrf_token())
Process.put(:plug_masked_csrf_token, token)
token
end
end
@doc """
Deletes the CSRF token from the process dictionary.
This will force the token to be deleted once the response is sent.
"""
def delete_csrf_token do
Process.delete(:plug_unmasked_csrf_token)
Process.delete(:plug_masked_csrf_token)
end
## Plug
@behaviour Plug
@token_size 16
@encoded_token_size 24
@double_encoded_token_size 32
def init(opts), do: opts
def call(conn, opts) do
csrf_token = get_csrf_from_session(conn)
Process.put(:plug_unmasked_csrf_token, csrf_token)
if not verified_request?(conn, csrf_token) do
conn = case Keyword.get(opts, :with, :exception) do
:exception ->
raise InvalidCSRFTokenError
:clear_session ->
conn |> configure_session(ignore: true) |> clear_session()
other ->
raise ArgumentError, "option :with should be one of :exception or :clear_session, got #{inspect other}"
end
end
register_before_send(conn, &ensure_same_origin_and_csrf_token!(&1, csrf_token))
end
## Verification
defp get_csrf_from_session(conn) do
if (csrf_token = get_session(conn, "_csrf_token")) &&
byte_size(csrf_token) == @encoded_token_size do
csrf_token
end
end
defp verified_request?(conn, csrf_token) do
conn.method in @unprotected_methods
|| valid_csrf_token?(csrf_token, conn.params["_csrf_token"])
|| valid_csrf_token?(csrf_token, get_req_header(conn, "x-csrf-token") |> List.first)
|| skip_csrf_protection?(conn)
end
defp valid_csrf_token?(<<csrf_token::@encoded_token_size-binary>>,
<<user_token::@double_encoded_token_size-binary, mask::@encoded_token_size-binary>>) do
case Base.decode64(user_token) do
{:ok, user_token} -> Plug.Crypto.masked_compare(csrf_token, user_token, mask)
:error -> false
end
end
defp valid_csrf_token?(_csrf_token, _user_token), do: false
## Before send
defp ensure_same_origin_and_csrf_token!(conn, csrf_token) do
if cross_origin_js?(conn) do
raise InvalidCrossOriginRequestError
end
ensure_csrf_token(conn, csrf_token)
end
defp cross_origin_js?(%Plug.Conn{method: "GET"} = conn),
do: not skip_csrf_protection?(conn) and not xhr?(conn) and js_content_type?(conn)
defp cross_origin_js?(%Plug.Conn{}),
do: false
defp js_content_type?(conn) do
conn
|> get_resp_header("content-type")
|> Enum.any?(&String.starts_with?(&1, ~w(text/javascript application/javascript)))
end
defp xhr?(conn) do
"XMLHttpRequest" in get_req_header(conn, "x-requested-with")
end
defp ensure_csrf_token(conn, csrf_token) do
Process.delete(:plug_masked_csrf_token)
case Process.delete(:plug_unmasked_csrf_token) do
^csrf_token -> conn
current -> put_session(conn, "_csrf_token", current)
end
end
## Helpers
defp skip_csrf_protection?(%Plug.Conn{private: %{plug_skip_csrf_protection: true}}), do: true
defp skip_csrf_protection?(%Plug.Conn{}), do: false
defp mask(token) do
mask = generate_token()
Base.encode64(Plug.Crypto.mask(token, mask)) <> mask
end
defp unmasked_csrf_token do
if token = Process.get(:plug_unmasked_csrf_token) do
token
else
token = generate_token()
Process.put(:plug_unmasked_csrf_token, token)
token
end
end
defp generate_token do
:crypto.strong_rand_bytes(@token_size) |> Base.encode64
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# The debugger is based on Better Errors, under MIT LICENSE.
#
# Copyright (c) 2012 Charlie Somerville
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
defmodule Plug.Debugger do
@moduledoc """
A module (**not a plug**) for debugging in development.
This module is commonly used within a `Plug.Builder` or a `Plug.Router`
and it wraps the `call/2` function.
Notice `Plug.Debugger` *does not* catch errors, as errors should still
propagate so that the Elixir process finishes with the proper reason.
This module does not perform any logging either, as all logging is done
by the web server handler.
**Note:** If this module is used with `Plug.ErrorHandler`, only one of
them will effectively handle errors. For this reason, it is recommended
that `Plug.Debugger` is used before `Plug.ErrorHandler` and only in
particular environments, like `:dev`.
## Examples
defmodule MyApp do
use Plug.Builder
if Mix.env == :dev do
use Plug.Debugger, otp_app: :my_app
end
plug :boom
def boom(conn, _) do
# Error raised here will be caught and displayed in a debug page
# complete with a stacktrace and other helpful info.
raise "oops"
end
end
## Options
* `:otp_app` - same as in `wrap/3`
## Links to the text editor
If a `PLUG_EDITOR` environment variable is set, `Plug.Debugger` will
use it to generate links to your text editor. The variable should be
set with `__FILE__` and `__LINE__` placeholders which will be correctly
replaced. For example (with the [TextMate](http://macromates.com) editor):
txmt://open/?url=file://__FILE__&line=__LINE__
"""
@already_sent {:plug_conn, :sent}
import Plug.Conn
@doc false
defmacro __using__(opts) do
quote do
@plug_debugger unquote(opts)
@before_compile Plug.Debugger
end
end
@doc false
defmacro __before_compile__(_) do
quote location: :keep do
defoverridable [call: 2]
def call(conn, opts) do
try do
super(conn, opts)
catch
kind, reason ->
Plug.Debugger.__catch__(conn, kind, reason, @plug_debugger)
end
end
end
end
@doc false
def __catch__(_conn, :error, %Plug.Conn.WrapperError{} = wrapper, opts) do
%{conn: conn, kind: kind, reason: reason, stack: stack} = wrapper
__catch__(conn, kind, reason, stack, opts)
end
def __catch__(conn, kind, reason, opts) do
__catch__(conn, kind, reason, System.stacktrace, opts)
end
defp __catch__(conn, kind, reason, stack, opts) do
receive do
@already_sent ->
send self(), @already_sent
:erlang.raise kind, reason, stack
after
0 ->
render conn, kind, reason, stack, opts
:erlang.raise kind, reason, stack
end
end
## Rendering
require EEx
EEx.function_from_file :defp, :template, "lib/plug/templates/debugger.eex", [:assigns]
# Made public with @doc false for testing.
@doc false
def render(conn, kind, reason, stack, opts) do
session = maybe_fetch_session(conn)
params = maybe_fetch_query_params(conn)
reason = Exception.normalize(kind, reason, stack)
{status, title, message} = info(kind, reason)
conn = put_resp_content_type(conn, "text/html")
send_resp conn, status, template(conn: conn, frames: frames(stack, opts),
title: title, message: message,
session: session, params: params)
end
defp maybe_fetch_session(conn) do
if conn.private[:plug_session_fetch] do
fetch_session(conn).private[:plug_session]
end
end
defp maybe_fetch_query_params(conn) do
fetch_query_params(conn).params
end
defp info(:error, error) do
{Plug.Exception.status(error), inspect(error.__struct__), Exception.message(error)}
end
defp info(:throw, thrown) do
{500, "unhandled throw", inspect(thrown)}
end
defp info(:exit, reason) do
{500, "unhandled exit", Exception.format_exit(reason)}
end
defp frames(stacktrace, opts) do
app = opts[:otp_app]
editor = System.get_env("PLUG_EDITOR")
stacktrace
|> Enum.map_reduce(0, &each_frame(&1, &2, app, editor))
|> elem(0)
end
defp each_frame(entry, index, root, editor) do
{module, info, location, app, func, args} = get_entry(entry)
{file, line} = {to_string(location[:file] || "nofile"), location[:line]}
source = get_source(module, file)
context = get_context(root, app)
snippet = get_snippet(source, line)
{%{app: app,
info: info,
file: file,
line: line,
context: context,
snippet: snippet,
index: index,
func: func,
args: args,
link: editor && get_editor(source, line, editor)
}, index + 1}
end
# From :elixir_compiler_*
defp get_entry({module, :__MODULE__, 0, location}) do
{module, inspect(module) <> " (module)", location, get_app(module), nil, []}
end
# From :elixir_compiler_*
defp get_entry({_module, :__MODULE__, 1, location}) do
{nil, "(module)", location, nil, nil, []}
end
# From :elixir_compiler_*
defp get_entry({_module, :__FILE__, 1, location}) do
{nil, "(file)", location, nil, nil, []}
end
defp get_entry({module, fun, args, location}) when is_list(args) do
{module, Exception.format_mfa(module, fun, length(args)), location, get_app(module), fun, args}
end
defp get_entry({module, fun, arity, location}) do
{module, Exception.format_mfa(module, fun, arity), location, get_app(module), fun, []}
end
defp get_entry({fun, arity, location}) do
{nil, Exception.format_fa(fun, arity), location, nil, fun, []}
end
defp get_app(module) do
case :application.get_application(module) do
{:ok, app} -> app
:undefined -> nil
end
end
defp get_context(app, app) when app != nil, do: :app
defp get_context(_app1, _app2), do: :all
defp get_source(module, file) do
cond do
File.regular?(file) ->
file
Code.ensure_loaded?(module) &&
(source = module.module_info(:compile)[:source]) ->
to_string(source)
true ->
file
end
end
defp get_editor(file, line, editor) do
editor
|> :binary.replace("__FILE__", URI.encode(Path.expand(file)))
|> :binary.replace("__LINE__", to_string(line))
|> h
end
@radius 5
defp get_snippet(file, line) do
if File.regular?(file) and is_integer(line) do
to_discard = max(line - @radius - 1, 0)
lines = File.stream!(file) |> Stream.take(line + 5) |> Stream.drop(to_discard)
{first_five, lines} = Enum.split(lines, line - to_discard - 1)
first_five = with_line_number first_five, to_discard + 1, false
{center, last_five} = Enum.split(lines, 1)
center = with_line_number center, line, true
last_five = with_line_number last_five, line + 1, false
first_five ++ center ++ last_five
end
end
defp with_line_number(lines, initial, highlight) do
Enum.map_reduce(lines, initial, fn(line, acc) ->
{{acc, line, highlight}, acc + 1}
end) |> elem(0)
end
## Helpers
defp method(%Plug.Conn{method: method}), do:
method
defp url(%Plug.Conn{scheme: scheme, host: host, port: port} = conn), do:
"#{scheme}://#{host}:#{port}#{conn.request_path}"
defp peer(%Plug.Conn{peer: {host, port}}), do:
"#{:inet_parse.ntoa host}:#{port}"
defp h(string) do
for <<code <- to_string(string)>> do
<< case code do
?& -> "&"
?< -> "<"
?> -> ">"
?" -> """
_ -> <<code>>
end :: binary >>
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
defmodule Plug.ErrorHandler do
@moduledoc """
A module to be used in your existing plugs in order to provide
error handling.
defmodule AppRouter do
use Plug.Router
use Plug.ErrorHandler
plug :match
plug :dispatch
get "/hello" do
send_resp(conn, 200, "world")
end
defp handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
send_resp(conn, conn.status, "Something went wrong")
end
end
Once this module is used, a callback named `handle_errors/2` should
be defined in your plug. This callback should accept a connection and a map
containing:
* the exception kind (`:throw`, `:error` or `:exit`),
* the reason (an exception for errors or a term for others)
* the stacktrace
After the callback is invoked, the error is re-raised.
It is advised to do as little work as possible when handling errors
and avoid accessing data like parameters and session, as the parsing
of those is what could have led the error to trigger in the first place.
Also notice that these pages are going to be shown in production. If
you are looking for error handling to help during development, consider
using `Plug.Debugger`.
**Note:** If this module is used with `Plug.Debugger`, it must be used
after `Plug.Debugger`.
"""
@doc false
defmacro __using__(_) do
quote location: :keep do
@before_compile Plug.ErrorHandler
defp handle_errors(conn, assigns) do
Plug.Conn.send_resp(conn, conn.status, "Something went wrong")
end
defoverridable [handle_errors: 2]
end
end
@doc false
defmacro __before_compile__(_env) do
quote location: :keep do
defoverridable [call: 2]
def call(conn, opts) do
try do
super(conn, opts)
catch
kind, reason ->
Plug.ErrorHandler.__catch__(conn, kind, reason, &handle_errors/2)
end
end
end
end
@already_sent {:plug_conn, :sent}
@doc false
def __catch__(_conn, :error, %Plug.Conn.WrapperError{} = wrapper, handle_errors) do
%{conn: conn, kind: kind, reason: reason, stack: stack} = wrapper
__catch__(conn, kind, reason, stack, handle_errors)
end
def __catch__(conn, kind, reason, handle_errors) do
__catch__(conn, kind, reason, System.stacktrace, handle_errors)
end
defp __catch__(conn, kind, reason, stack, handle_errors) do
receive do
@already_sent ->
send self(), @already_sent
after
0 ->
reason = Exception.normalize(kind, reason, stack)
conn
|> Plug.Conn.put_status(status(kind, reason))
|> handle_errors.(%{kind: kind, reason: reason, stack: stack})
end
:erlang.raise(kind, reason, stack)
end
defp status(:error, error), do: Plug.Exception.status(error)
defp status(:throw, _throw), do: 500
defp status(:exit, _exit), do: 500
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# This file defines the Plug.Exception protocol and
# the exceptions that implement such protocol.
defprotocol Plug.Exception do
@moduledoc """
A protocol that extends exceptions to be status-code aware.
By default, it looks for an implementation of the protocol,
otherwise checks if the exception has the `:plug_status` field
or simply returns 500.
"""
@fallback_to_any true
@doc """
Receives an exception and returns its HTTP status code.
"""
@spec status(t) :: Plug.Conn.status
def status(exception)
end
defimpl Plug.Exception, for: Any do
def status(%{plug_status: status}) when is_integer(status), do: status
def status(_), do: 500
end
|
> > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
defmodule Plug.Head do
@moduledoc """
A Plug to convert `HEAD` requests to `GET` requests.
## Examples
Plug.Head.call(conn, [])
"""
@behaviour Plug
alias Plug.Conn
def init([]), do: []
def call(%Conn{method: "HEAD"} = conn, []), do: %{conn | method: "GET"}
def call(conn, []), do: conn
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
defmodule Plug.HTML do
@moduledoc """
Conveniences for generating HTML.
"""
@doc ~S"""
Escapes the given HTML.
iex> Plug.HTML.html_escape("<foo>")
"<foo>"
iex> Plug.HTML.html_escape("quotes: \" & \'")
"quotes: " & '"
"""
def html_escape(data) when is_binary(data) do
IO.iodata_to_binary(for <<char <- data>>, do: escape_char(char))
end
@compile {:inline, escape_char: 1}
@escapes [
{?<, "<"},
{?>, ">"},
{?&, "&"},
{?", """},
{?', "'"}
]
Enum.each @escapes, fn { match, insert } ->
defp escape_char(unquote(match)), do: unquote(insert)
end
defp escape_char(char), do: char
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
defmodule Plug.Logger do
@moduledoc """
A plug for logging basic request information in the format:
GET /index.html
Sent 200 in 572ms
To use it, just plug it into the desired module.
plug Plug.Logger, log: :debug
## Options
* `:log` - The log level at which this plug should log its request info.
Default is `:info`.
"""
require Logger
alias Plug.Conn
@behaviour Plug
def init(opts) do
Keyword.get(opts, :log, :info)
end
def call(conn, level) do
Logger.log level, fn ->
[conn.method, ?\s, conn.request_path]
end
before_time = :os.timestamp()
Conn.register_before_send(conn, fn conn ->
Logger.log level, fn ->
after_time = :os.timestamp()
diff = :timer.now_diff(after_time, before_time)
[connection_type(conn), ?\s, Integer.to_string(conn.status),
" in ", formatted_diff(diff)]
end
conn
end)
end
defp formatted_diff(diff) when diff > 1000, do: [diff |> div(1000) |> Integer.to_string, "ms"]
defp formatted_diff(diff), do: [diff |> Integer.to_string, "µs"]
defp connection_type(%{state: :chunked}), do: "Chunked"
defp connection_type(_), do: "Sent"
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
defmodule Plug.MethodOverride do
@moduledoc """
This plug overrides the request's `POST` method with the method defined in
the `_method` request parameter.
The `POST` method can be overridden only by these HTTP methods:
* `PUT`
* `PATCH`
* `DELETE`
This plug expects the body parameters to be already parsed and
fetched. Those can be fetched with `Plug.Parsers`.
This plug doesn't accept any options.
## Examples
Plug.MethodOverride.call(conn, [])
"""
@behaviour Plug
@allowed_methods ~w(DELETE PUT PATCH)
def init([]), do: []
def call(%Plug.Conn{method: "POST", body_params: body_params} = conn, []),
do: override_method(conn, body_params)
def call(%Plug.Conn{} = conn, []),
do: conn
defp override_method(conn, %Plug.Conn.Unfetched{}) do
# Just skip it because maybe it is a content-type that
# we could not parse as parameters (for example, text/gps)
conn
end
defp override_method(conn, body_params) do
method = (body_params["_method"] || "") |> String.upcase
cond do
method in @allowed_methods -> %{conn | method: method}
true -> conn
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
defmodule Plug.MIME do
@moduledoc """
Maps MIME types to file extensions and vice versa.
MIME types can be extended in your application configuration
as follows:
config :plug, :mimes, %{
"application/vnd.api+json" => ["json-api"]
}
After adding the configuration, Plug needs to be recompiled.
If you are using mix, it can be done with:
$ touch deps/plug/mix.exs
$ mix deps.compile plug
"""
@compile :no_native
@default_type "application/octet-stream"
# Read all the MIME type mappings into the `mapping` variable.
@external_resource "lib/plug/mime.types"
stream = File.stream!("lib/plug/mime.types")
mapping = Enum.flat_map(stream, fn (line) ->
if String.starts_with?(line, ["#", "\n"]) do
[]
else
[type|exts] = line |> String.strip |> String.split
[{type, exts}]
end
end)
app = Application.get_env(:plug, :mimes, %{})
mapping = Enum.reduce app, mapping, fn {k, v}, acc ->
List.keystore(acc, k, 0, {k, v})
end
@doc """
Returns whether a MIME type is registered.
## Examples
iex> Plug.MIME.valid?("text/plain")
true
iex> Plug.MIME.valid?("foo/bar")
false
"""
@spec valid?(String.t) :: boolean
def valid?(type) do
is_list entry(type)
end
@doc """
Returns the extensions associated with a given MIME type.
## Examples
iex> Plug.MIME.extensions("text/html")
["html", "htm"]
iex> Plug.MIME.extensions("application/json")
["json"]
iex> Plug.MIME.extensions("foo/bar")
[]
"""
@spec extensions(String.t) :: [String.t]
def extensions(type) do
entry(type) || []
end
@doc """
Returns the MIME type associated with a file extension. If no MIME type is
known for `file_extension`, `#{inspect @default_type}` is returned.
## Examples
iex> Plug.MIME.type("txt")
"text/plain"
iex> Plug.MIME.type("foobarbaz")
#{inspect @default_type}
"""
@spec type(String.t) :: String.t
def type(file_extension)
for {type, exts} <- mapping, ext <- exts do
def type(unquote(ext)), do: unquote(type)
end
def type(_ext), do: @default_type
@doc """
Guesses the MIME type based on the path's extension. See `type/1`.
## Examples
iex> Plug.MIME.path("index.html")
"text/html"
"""
@spec path(Path.t) :: String.t
def path(path) do
case Path.extname(path) do
"." <> ext -> type(downcase(ext, ""))
_ -> @default_type
end
end
defp downcase(<<h, t::binary>>, acc) when h in ?A..?Z, do: downcase(t, <<acc::binary, h+32>>)
defp downcase(<<h, t::binary>>, acc), do: downcase(t, <<acc::binary, h>>)
defp downcase(<<>>, acc), do: acc
# entry/1
@spec entry(String.t) :: list(String.t)
for {type, exts} <- mapping do
defp entry(unquote(type)), do: unquote(exts)
end
defp entry(_type), do: nil
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 | # https://git.fedorahosted.org/cgit/mailcap.git/plain/mime.types # This file controls what Internet media types are sent to the client for # given file extension(s). Sending the correct media type to the client # is important so they know how to handle the content of the file. # Extra types can either be added here or by using an AddType directive # in your config files. For more information about Internet media types, # please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type # registry is at <http://www.iana.org/assignments/media-types/>. # IANA types # MIME type Extensions application/1d-interleaved-parityfec application/3gpdash-qoe-report+xml application/3gpp-ims+xml application/A2L a2l application/activemessage application/alto-costmap+json application/alto-costmapfilter+json application/alto-directory+json application/alto-endpointcost+json application/alto-endpointcostparams+json application/alto-endpointprop+json application/alto-endpointpropparams+json application/alto-error+json application/alto-networkmap+json application/alto-networkmapfilter+json application/AML aml application/andrew-inset ez application/applefile application/ATF atf application/ATFX atfx application/ATXML atxml application/atom+xml atom application/atomcat+xml atomcat application/atomdeleted+xml atomdeleted application/atomicmail application/atomsvc+xml atomsvc application/auth-policy+xml apxml application/bacnet-xdd+zip xdd application/batch-SMTP application/beep+xml application/calendar+json application/calendar+xml xcs application/call-completion application/cals-1840 application/cbor cbor application/ccmp+xml ccmp application/ccxml+xml ccxml application/CDFX+XML cdfx application/cdmi-capability cdmia application/cdmi-container cdmic application/cdmi-domain cdmid application/cdmi-object cdmio application/cdmi-queue cdmiq application/CEA cea application/cea-2018+xml application/cellml+xml cellml cml application/cfw application/cms cmsc application/cnrp+xml application/coap-group+json application/commonground application/conference-info+xml application/cpl+xml cpl application/csrattrs csrattrs application/csta+xml application/CSTAdata+xml application/cybercash application/dash+xml mpd application/dashdelta mpdd application/davmount+xml davmount application/dca-rft application/DCD dcd application/dec-dx application/dialog-info+xml application/dicom dcm application/DII dii application/DIT dit application/dns application/dskpp+xml xmls application/dssc+der dssc application/dssc+xml xdssc application/dvcs dvc application/ecmascript es application/EDI-Consent application/EDI-X12 application/EDIFACT application/emma+xml emma application/emotionml+xml emotionml application/encaprtp application/epp+xml application/epub+zip epub application/eshop application/exi exi application/fastinfoset finf application/fastsoap application/fdt+xml fdt # fits, fit, fts: image/fits application/fits application/font-sfnt ttf application/font-tdpfr pfr application/font-woff woff application/framework-attributes+xml application/gzip gz tgz application/H224 application/held+xml application/http application/hyperstudio stk application/ibe-key-request+xml application/ibe-pkg-reply+xml application/ibe-pp-data application/iges application/im-iscomposing+xml application/index application/index.cmd application/index.obj application/index.response application/index.vnd application/inkml+xml ink inkml application/iotp application/ipfix ipfix application/ipp application/isup application/its+xml its application/javascript js application/jose application/jose+json application/jrd+json jrd application/json json application/json-patch+json json-patch application/json-seq application/jwk+json application/jwk-set+json application/jwt application/kpml-request+xml application/kpml-response+xml application/ld+json jsonld application/link-format wlnk application/load-control+xml application/lost+xml lostxml application/lostsync+xml lostsyncxml application/LXF lxf application/mac-binhex40 hqx application/macwriteii application/mads+xml mads application/marc mrc application/marcxml+xml mrcx application/mathematica nb ma mb application/mathml-content+xml application/mathml-presentation+xml application/mathml+xml mml application/mbms-associated-procedure-description+xml application/mbms-deregister+xml application/mbms-envelope+xml application/mbms-msk-response+xml application/mbms-msk+xml application/mbms-protection-description+xml application/mbms-reception-report+xml application/mbms-register-response+xml application/mbms-register+xml application/mbms-schedule+xml application/mbms-user-service-description+xml application/mbox mbox application/media_control+xml # mpf: text/vnd.ms-mediapackage application/media-policy-dataset+xml application/mediaservercontrol+xml application/merge-patch+json application/metalink4+xml meta4 application/mets+xml mets application/MF4 mf4 application/mikey application/mods+xml mods application/moss-keys application/moss-signature application/mosskey-data application/mosskey-request application/mp21 m21 mp21 # mp4, mpg4: video/mp4, see RFC 4337 application/mp4 application/mpeg4-generic application/mpeg4-iod application/mpeg4-iod-xmt # xdf: application/xcap-diff+xml application/mrb-consumer+xml application/mrb-publish+xml application/msc-ivr+xml application/msc-mixer+xml application/msword doc application/mxf mxf application/nasdata application/news-checkgroups application/news-groupinfo application/news-transmission application/nlsml+xml application/nss application/ocsp-request orq application/ocsp-response ors application/octet-stream bin lha lzh exe class so dll img iso application/oda oda application/ODX odx application/oebps-package+xml opf application/ogg ogx application/oxps oxps application/p2p-overlay+xml relo application/parityfec # xer: application/xcap-error+xml application/patch-ops-error+xml application/pdf pdf application/PDX pdx application/pgp-encrypted pgp application/pgp-keys application/pgp-signature sig application/pidf-diff+xml application/pidf+xml application/pkcs10 p10 application/pkcs7-mime p7m p7c application/pkcs7-signature p7s application/pkcs8 p8 # ac: application/vnd.nokia.n-gage.ac+xml application/pkix-attr-cert application/pkix-cert cer application/pkix-crl crl application/pkix-pkipath pkipath application/pkixcmp pki application/pls+xml pls application/poc-settings+xml application/postscript ps eps ai application/provenance+xml provx application/prs.alvestrand.titrax-sheet application/prs.cww cw cww application/prs.hpub+zip hpub application/prs.nprend rnd rct application/prs.plucker application/prs.rdf-xml-crypt rdf-crypt application/prs.xsf+xml xsf application/pskc+xml pskcxml application/qsig application/raptorfec application/rdap+json application/rdf+xml rdf application/reginfo+xml rif application/relax-ng-compact-syntax rnc application/remote-printing application/reputon+json application/resource-lists-diff+xml rld application/resource-lists+xml rl application/riscos application/rlmi+xml application/rls-services+xml rs application/rpki-ghostbusters gbr application/rpki-manifest mft application/rpki-roa roa application/rpki-updown application/rtf rtf application/rtploopback application/rtx application/samlassertion+xml application/samlmetadata+xml application/sbml+xml application/scaip+xml application/scvp-cv-request scq application/scvp-cv-response scs application/scvp-vp-request spq application/scvp-vp-response spp application/sdp sdp application/sep+xml application/sep-exi application/session-info application/set-payment application/set-payment-initiation application/set-registration application/set-registration-initiation application/sgml application/sgml-open-catalog soc application/shf+xml shf application/sieve siv sieve application/simple-filter+xml cl application/simple-message-summary application/simpleSymbolContainer application/slate # application/smil obsoleted by application/smil+xml application/smil+xml smil smi sml application/smpte336m application/soap+fastinfoset application/soap+xml application/sparql-query rq application/sparql-results+xml srx application/spirits-event+xml application/sql sql application/srgs gram application/srgs+xml grxml application/sru+xml sru application/ssml+xml ssml application/tamp-apex-update tau application/tamp-apex-update-confirm auc application/tamp-community-update tcu application/tamp-community-update-confirm cuc application/tamp-error ter application/tamp-sequence-adjust tsa application/tamp-sequence-adjust-confirm sac # tsq: application/timestamp-query application/tamp-status-query # tsr: application/timestamp-reply application/tamp-status-response application/tamp-update tur application/tamp-update-confirm tuc application/tei+xml tei teiCorpus odd application/thraud+xml tfi application/timestamp-query tsq application/timestamp-reply tsr application/timestamped-data tsd application/ttml+xml ttml application/tve-trigger application/ulpfec application/urc-grpsheet+xml gsheet application/urc-ressheet+xml rsheet application/urc-targetdesc+xml td application/urc-uisocketdesc+xml uis application/vcard+json application/vcard+xml application/vemmi application/vnd.3gpp.bsf+xml application/vnd.3gpp.pic-bw-large plb application/vnd.3gpp.pic-bw-small psb application/vnd.3gpp.pic-bw-var pvb # sms: application/vnd.3gpp2.sms application/vnd.3gpp.sms application/vnd.3gpp2.bcmcsinfo+xml application/vnd.3gpp2.sms sms application/vnd.3gpp2.tcap tcap application/vnd.3M.Post-it-Notes pwn application/vnd.accpac.simply.aso aso application/vnd.accpac.simply.imp imp application/vnd.acucobol acu application/vnd.acucorp atc acutc # swf: application/x-shockwave-flash for now application/vnd.adobe.flash.movie application/vnd.adobe.formscentral.fcdt fcdt application/vnd.adobe.fxp fxp fxpl application/vnd.adobe.partial-upload application/vnd.adobe.xdp+xml xdp application/vnd.adobe.xfdf xfdf application/vnd.aether.imp application/vnd.ah-barcode application/vnd.ahead.space ahead application/vnd.airzip.filesecure.azf azf application/vnd.airzip.filesecure.azs azs application/vnd.americandynamics.acc acc application/vnd.amiga.ami ami application/vnd.amundsen.maze+xml application/vnd.anser-web-certificate-issue-initiation cii # Not in IANA listing, but is on FTP site? application/vnd.anser-web-funds-transfer-initiation fti # atx: audio/ATRAC-X application/vnd.antix.game-component application/vnd.apache.thrift.binary application/vnd.apache.thrift.compact application/vnd.apache.thrift.json application/vnd.api+json application/vnd.apple.installer+xml dist distz pkg mpkg # m3u: audio/x-mpegurl for now application/vnd.apple.mpegurl m3u8 # application/vnd.arastra.swi obsoleted by application/vnd.aristanetworks.swi application/vnd.aristanetworks.swi swi application/vnd.artsquare application/vnd.astraea-software.iota iota application/vnd.audiograph aep application/vnd.autopackage package application/vnd.avistar+xml application/vnd.balsamiq.bmml+xml bmml application/vnd.bekitzur-stech+json application/vnd.blueice.multipass mpm application/vnd.bluetooth.ep.oob ep application/vnd.bluetooth.le.oob le application/vnd.bmi bmi application/vnd.businessobjects rep application/vnd.cab-jscript application/vnd.canon-cpdl application/vnd.canon-lips application/vnd.cendio.thinlinc.clientconf tlclient application/vnd.century-systems.tcp_stream application/vnd.chemdraw+xml cdxml application/vnd.chipnuts.karaoke-mmd mmd application/vnd.cinderella cdy application/vnd.cirpack.isdn-ext application/vnd.citationstyles.style+xml csl application/vnd.claymore cla application/vnd.cloanto.rp9 rp9 application/vnd.clonk.c4group c4g c4d c4f c4p c4u application/vnd.cluetrust.cartomobile-config c11amc application/vnd.cluetrust.cartomobile-config-pkg c11amz application/vnd.coffeescript coffee application/vnd.collection+json application/vnd.collection.doc+json application/vnd.collection.next+json # icc: application/vnd.iccprofile application/vnd.commerce-battelle ica icf icd ic0 ic1 ic2 ic3 ic4 ic5 ic6 ic7 ic8 application/vnd.commonspace csp cst application/vnd.contact.cmsg cdbcmsg application/vnd.cosmocaller cmc application/vnd.crick.clicker clkx application/vnd.crick.clicker.keyboard clkk application/vnd.crick.clicker.palette clkp application/vnd.crick.clicker.template clkt application/vnd.crick.clicker.wordbank clkw application/vnd.criticaltools.wbs+xml wbs application/vnd.ctc-posml pml application/vnd.ctct.ws+xml application/vnd.cups-pdf application/vnd.cups-postscript application/vnd.cups-ppd ppd application/vnd.cups-raster application/vnd.cups-raw application/vnd.curl curl application/vnd.cyan.dean.root+xml application/vnd.cybank application/vnd.dart dart application/vnd.data-vision.rdz rdz application/vnd.debian.binary-package deb udeb application/vnd.dece.data uvf uvvf uvd uvvd application/vnd.dece.ttml+xml uvt uvvt application/vnd.dece.unspecified uvx uvvx application/vnd.dece.zip uvz uvvz application/vnd.denovo.fcselayout-link fe_launch application/vnd.desmume.movie dsm application/vnd.dir-bi.plate-dl-nosuffix application/vnd.dm.delegation+xml application/vnd.dna dna application/vnd.document+json docjson application/vnd.dolby.mobile.1 application/vnd.dolby.mobile.2 application/vnd.doremir.scorecloud-binary-document scld application/vnd.dpgraph dpg mwc dpgraph application/vnd.dreamfactory dfac application/vnd.dtg.local application/vnd.dtg.local.flash fla application/vnd.dtg.local.html application/vnd.dvb.ait ait # class: application/octet-stream application/vnd.dvb.dvbj application/vnd.dvb.esgcontainer application/vnd.dvb.ipdcdftnotifaccess application/vnd.dvb.ipdcesgaccess application/vnd.dvb.ipdcesgaccess2 application/vnd.dvb.ipdcesgpdd application/vnd.dvb.ipdcroaming application/vnd.dvb.iptv.alfec-base application/vnd.dvb.iptv.alfec-enhancement application/vnd.dvb.notif-aggregate-root+xml application/vnd.dvb.notif-container+xml application/vnd.dvb.notif-generic+xml application/vnd.dvb.notif-ia-msglist+xml application/vnd.dvb.notif-ia-registration-request+xml application/vnd.dvb.notif-ia-registration-response+xml application/vnd.dvb.notif-init+xml # pfr: application/font-tdpfr application/vnd.dvb.pfr application/vnd.dvb.service svc # dxr: application/x-director application/vnd.dxr application/vnd.dynageo geo application/vnd.dzr dzr application/vnd.easykaraoke.cdgdownload application/vnd.ecdis-update application/vnd.ecowin.chart mag application/vnd.ecowin.filerequest application/vnd.ecowin.fileupdate application/vnd.ecowin.series application/vnd.ecowin.seriesrequest application/vnd.ecowin.seriesupdate application/vnd.enliven nml application/vnd.enphase.envoy application/vnd.eprints.data+xml application/vnd.epson.esf esf application/vnd.epson.msf msf application/vnd.epson.quickanime qam application/vnd.epson.salt slt application/vnd.epson.ssf ssf application/vnd.ericsson.quickcall qcall qca application/vnd.eszigno3+xml es3 et3 application/vnd.etsi.aoc+xml application/vnd.etsi.asic-e+zip asice sce # scs: application/scvp-cv-response application/vnd.etsi.asic-s+zip asics application/vnd.etsi.cug+xml application/vnd.etsi.iptvcommand+xml application/vnd.etsi.iptvdiscovery+xml application/vnd.etsi.iptvprofile+xml application/vnd.etsi.iptvsad-bc+xml application/vnd.etsi.iptvsad-cod+xml application/vnd.etsi.iptvsad-npvr+xml application/vnd.etsi.iptvservice+xml application/vnd.etsi.iptvsync+xml application/vnd.etsi.iptvueprofile+xml application/vnd.etsi.mcid+xml application/vnd.etsi.mheg5 application/vnd.etsi.overload-control-policy-dataset+xml application/vnd.etsi.pstn+xml application/vnd.etsi.sci+xml application/vnd.etsi.simservs+xml application/vnd.etsi.timestamp-token tst application/vnd.etsi.tsl.der application/vnd.etsi.tsl+xml application/vnd.eudora.data application/vnd.ezpix-album ez2 application/vnd.ezpix-package ez3 application/vnd.f-secure.mobile application/vnd.fastcopy-disk-image dim application/vnd.fdf fdf application/vnd.fdsn.mseed msd mseed application/vnd.fdsn.seed seed dataless application/vnd.ffsns # all extensions: application/vnd.hbci application/vnd.fints application/vnd.FloGraphIt gph application/vnd.fluxtime.clip ftc application/vnd.font-fontforge-sfd sfd application/vnd.framemaker fm application/vnd.frogans.fnc fnc application/vnd.frogans.ltf ltf application/vnd.fsc.weblaunch fsc application/vnd.fujitsu.oasys oas application/vnd.fujitsu.oasys2 oa2 application/vnd.fujitsu.oasys3 oa3 application/vnd.fujitsu.oasysgp fg5 application/vnd.fujitsu.oasysprs bh2 application/vnd.fujixerox.ART-EX application/vnd.fujixerox.ART4 application/vnd.fujixerox.ddd ddd application/vnd.fujixerox.docuworks xdw application/vnd.fujixerox.docuworks.binder xbd application/vnd.fujixerox.docuworks.container xct application/vnd.fujixerox.HBPL application/vnd.fut-misnet application/vnd.fuzzysheet fzs application/vnd.genomatix.tuxedo txd application/vnd.geo+json geojson application/vnd.geocube+xml g3 g³ application/vnd.geogebra.file ggb application/vnd.geogebra.tool ggt application/vnd.geometry-explorer gex gre application/vnd.geonext gxt application/vnd.geoplan g2w application/vnd.geospace g3w # gbr: application/rpki-ghostbusters application/vnd.gerber application/vnd.globalplatform.card-content-mgt application/vnd.globalplatform.card-content-mgt-response application/vnd.gmx gmx application/vnd.google-earth.kml+xml kml application/vnd.google-earth.kmz kmz application/vnd.gov.sk.e-form+xml application/vnd.gov.sk.e-form+zip application/vnd.gov.sk.xmldatacontainer+xml application/vnd.grafeq gqf gqs application/vnd.gridmp application/vnd.groove-account gac application/vnd.groove-help ghf application/vnd.groove-identity-message gim application/vnd.groove-injector grv application/vnd.groove-tool-message gtm application/vnd.groove-tool-template tpl application/vnd.groove-vcard vcg application/vnd.hal+json application/vnd.hal+xml hal application/vnd.HandHeld-Entertainment+xml zmm application/vnd.hbci hbci hbc kom upa pkd bpd # rep: application/vnd.businessobjects application/vnd.hcl-bireports application/vnd.heroku+json application/vnd.hhe.lesson-player les application/vnd.hp-HPGL hpgl application/vnd.hp-hpid hpi hpid application/vnd.hp-hps hps application/vnd.hp-jlyt jlt application/vnd.hp-PCL pcl application/vnd.hp-PCLXL application/vnd.httphone application/vnd.hydrostatix.sof-data sfd-hdstx application/vnd.hzn-3d-crossword x3d application/vnd.ibm.afplinedata application/vnd.ibm.electronic-media emm application/vnd.ibm.MiniPay mpy application/vnd.ibm.modcap list3820 listafp afp pseg3820 application/vnd.ibm.rights-management irm application/vnd.ibm.secure-container sc application/vnd.iccprofile icc icm application/vnd.ieee.1905 1905.1 application/vnd.igloader igl application/vnd.immervision-ivp ivp application/vnd.immervision-ivu ivu application/vnd.ims.imsccv1p1 imscc application/vnd.ims.imsccv1p2 application/vnd.ims.imsccv1p3 application/vnd.ims.lis.v2.result+json application/vnd.ims.lti.v2.toolconsumerprofile+json application/vnd.ims.lti.v2.toolproxy.id+json application/vnd.ims.lti.v2.toolproxy+json application/vnd.ims.lti.v2.toolsettings+json application/vnd.ims.lti.v2.toolsettings.simple+json application/vnd.informedcontrol.rms+xml # application/vnd.informix-visionary obsoleted by application/vnd.visionary application/vnd.infotech.project application/vnd.infotech.project+xml application/vnd.innopath.wamp.notification application/vnd.insors.igm igm application/vnd.intercon.formnet xpw xpx application/vnd.intergeo i2g application/vnd.intertrust.digibox application/vnd.intertrust.nncp application/vnd.intu.qbo qbo application/vnd.intu.qfx qfx application/vnd.iptc.g2.catalogitem+xml application/vnd.iptc.g2.conceptitem+xml application/vnd.iptc.g2.knowledgeitem+xml application/vnd.iptc.g2.newsitem+xml application/vnd.iptc.g2.newsmessage+xml application/vnd.iptc.g2.packageitem+xml application/vnd.iptc.g2.planningitem+xml application/vnd.ipunplugged.rcprofile rcprofile application/vnd.irepository.package+xml irp application/vnd.is-xpr xpr application/vnd.isac.fcs fcs application/vnd.jam jam application/vnd.japannet-directory-service application/vnd.japannet-jpnstore-wakeup application/vnd.japannet-payment-wakeup application/vnd.japannet-registration application/vnd.japannet-registration-wakeup application/vnd.japannet-setstore-wakeup application/vnd.japannet-verification application/vnd.japannet-verification-wakeup application/vnd.jcp.javame.midlet-rms rms application/vnd.jisp jisp application/vnd.joost.joda-archive joda application/vnd.jsk.isdn-ngn application/vnd.kahootz ktz ktr application/vnd.kde.karbon karbon application/vnd.kde.kchart chrt application/vnd.kde.kformula kfo application/vnd.kde.kivio flw application/vnd.kde.kontour kon application/vnd.kde.kpresenter kpr kpt application/vnd.kde.kspread ksp application/vnd.kde.kword kwd kwt application/vnd.kenameaapp htke application/vnd.kidspiration kia application/vnd.Kinar kne knp sdf application/vnd.koan skp skd skm skt application/vnd.kodak-descriptor sse application/vnd.las.las+xml lasxml application/vnd.liberty-request+xml application/vnd.llamagraphics.life-balance.desktop lbd application/vnd.llamagraphics.life-balance.exchange+xml lbe application/vnd.lotus-1-2-3 123 wk4 wk3 wk1 application/vnd.lotus-approach apr vew application/vnd.lotus-freelance prz pre application/vnd.lotus-notes nsf ntf ndl ns4 ns3 ns2 nsh nsg application/vnd.lotus-organizer or3 or2 org application/vnd.lotus-screencam scm application/vnd.lotus-wordpro lwp sam application/vnd.macports.portpkg portpkg application/vnd.marlin.drm.actiontoken+xml application/vnd.marlin.drm.conftoken+xml application/vnd.marlin.drm.license+xml application/vnd.marlin.drm.mdcf mdc application/vnd.mason+json application/vnd.maxmind.maxmind-db mmdb application/vnd.mcd mcd application/vnd.medcalcdata mc1 application/vnd.mediastation.cdkey cdkey application/vnd.meridian-slingshot application/vnd.MFER mwf application/vnd.mfmp mfm application/vnd.micrografx.flo flo application/vnd.micrografx.igx igx application/vnd.miele+json application/vnd.mif mif application/vnd.minisoft-hp3000-save application/vnd.mitsubishi.misty-guard.trustweb application/vnd.Mobius.DAF daf application/vnd.Mobius.DIS dis application/vnd.Mobius.MBK mbk application/vnd.Mobius.MQY mqy application/vnd.Mobius.MSL msl application/vnd.Mobius.PLC plc application/vnd.Mobius.TXF txf application/vnd.mophun.application mpn application/vnd.mophun.certificate mpc application/vnd.motorola.flexsuite application/vnd.motorola.flexsuite.adsi application/vnd.motorola.flexsuite.fis application/vnd.motorola.flexsuite.gotap application/vnd.motorola.flexsuite.kmr application/vnd.motorola.flexsuite.ttc application/vnd.motorola.flexsuite.wem application/vnd.motorola.iprm application/vnd.mozilla.xul+xml xul application/vnd.ms-3mfdocument 3mf application/vnd.ms-artgalry cil application/vnd.ms-asf asf application/vnd.ms-cab-compressed cab application/vnd.ms-excel xls xlm xla xlc xlt xlw application/vnd.ms-excel.template.macroEnabled.12 xltm application/vnd.ms-excel.addin.macroEnabled.12 xlam application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb application/vnd.ms-excel.sheet.macroEnabled.12 xlsm application/vnd.ms-fontobject eot application/vnd.ms-htmlhelp chm application/vnd.ms-ims ims application/vnd.ms-lrm lrm application/vnd.ms-office.activeX+xml application/vnd.ms-officetheme thmx application/vnd.ms-playready.initiator+xml application/vnd.ms-powerpoint ppt pps pot application/vnd.ms-powerpoint.addin.macroEnabled.12 ppam application/vnd.ms-powerpoint.presentation.macroEnabled.12 pptm application/vnd.ms-powerpoint.slide.macroEnabled.12 sldm application/vnd.ms-powerpoint.slideshow.macroEnabled.12 ppsm application/vnd.ms-powerpoint.template.macroEnabled.12 potm application/vnd.ms-project mpp mpt application/vnd.ms-tnef tnef tnf application/vnd.ms-windows.printerpairing application/vnd.ms-wmdrm.lic-chlg-req application/vnd.ms-wmdrm.lic-resp application/vnd.ms-wmdrm.meter-chlg-req application/vnd.ms-wmdrm.meter-resp application/vnd.ms-word.document.macroEnabled.12 docm application/vnd.ms-word.template.macroEnabled.12 dotm application/vnd.ms-works wcm wdb wks wps application/vnd.ms-wpl wpl application/vnd.ms-xpsdocument xps application/vnd.msa-disk-image msa application/vnd.mseq mseq application/vnd.msign application/vnd.multiad.creator crtr application/vnd.multiad.creator.cif cif application/vnd.music-niff application/vnd.musician mus application/vnd.muvee.style msty application/vnd.mynfc taglet application/vnd.ncd.control application/vnd.ncd.reference application/vnd.nervana entity request bkm kcm application/vnd.netfpx # ntf: application/vnd.lotus-notes application/vnd.nitf nitf application/vnd.neurolanguage.nlu nlu application/vnd.nintendo.nitro.rom nds application/vnd.nintendo.snes.rom sfc smc application/vnd.noblenet-directory nnd application/vnd.noblenet-sealer nns application/vnd.noblenet-web nnw application/vnd.nokia.catalogs application/vnd.nokia.conml+wbxml application/vnd.nokia.conml+xml application/vnd.nokia.iptv.config+xml application/vnd.nokia.iSDS-radio-presets application/vnd.nokia.landmark+wbxml application/vnd.nokia.landmark+xml application/vnd.nokia.landmarkcollection+xml application/vnd.nokia.n-gage.ac+xml ac application/vnd.nokia.n-gage.data ngdat application/vnd.nokia.n-gage.symbian.install n-gage application/vnd.nokia.ncd application/vnd.nokia.pcd+wbxml application/vnd.nokia.pcd+xml application/vnd.nokia.radio-preset rpst application/vnd.nokia.radio-presets rpss application/vnd.novadigm.EDM edm application/vnd.novadigm.EDX edx application/vnd.novadigm.EXT ext application/vnd.ntt-local.content-share application/vnd.ntt-local.file-transfer application/vnd.ntt-local.ogw_remote-access application/vnd.ntt-local.sip-ta_remote application/vnd.ntt-local.sip-ta_tcp_stream application/vnd.oasis.opendocument.chart odc application/vnd.oasis.opendocument.chart-template otc application/vnd.oasis.opendocument.database odb application/vnd.oasis.opendocument.formula odf application/vnd.oasis.opendocument.formula-template otf application/vnd.oasis.opendocument.graphics odg application/vnd.oasis.opendocument.graphics-template otg application/vnd.oasis.opendocument.image odi application/vnd.oasis.opendocument.image-template oti application/vnd.oasis.opendocument.presentation odp application/vnd.oasis.opendocument.presentation-template otp application/vnd.oasis.opendocument.spreadsheet ods application/vnd.oasis.opendocument.spreadsheet-template ots application/vnd.oasis.opendocument.text odt application/vnd.oasis.opendocument.text-master odm application/vnd.oasis.opendocument.text-template ott application/vnd.oasis.opendocument.text-web oth application/vnd.obn application/vnd.oftn.l10n+json application/vnd.oipf.contentaccessdownload+xml application/vnd.oipf.contentaccessstreaming+xml application/vnd.oipf.cspg-hexbinary application/vnd.oipf.dae.svg+xml application/vnd.oipf.dae.xhtml+xml application/vnd.oipf.mippvcontrolmessage+xml application/vnd.oipf.pae.gem application/vnd.oipf.spdiscovery+xml application/vnd.oipf.spdlist+xml application/vnd.oipf.ueprofile+xml application/vnd.olpc-sugar xo application/vnd.oma.bcast.associated-procedure-parameter+xml application/vnd.oma.bcast.drm-trigger+xml application/vnd.oma.bcast.imd+xml application/vnd.oma.bcast.ltkm application/vnd.oma.bcast.notification+xml application/vnd.oma.bcast.provisioningtrigger application/vnd.oma.bcast.sgboot application/vnd.oma.bcast.sgdd+xml application/vnd.oma.bcast.sgdu application/vnd.oma.bcast.simple-symbol-container application/vnd.oma.bcast.smartcard-trigger+xml application/vnd.oma.bcast.sprov+xml application/vnd.oma.bcast.stkm application/vnd.oma.cab-address-book+xml application/vnd.oma.cab-feature-handler+xml application/vnd.oma.cab-pcc+xml application/vnd.oma.cab-subs-invite+xml application/vnd.oma.cab-user-prefs+xml application/vnd.oma.dcd application/vnd.oma.dcdc application/vnd.oma.dd2+xml dd2 application/vnd.oma.drm.risd+xml application/vnd.oma.group-usage-list+xml application/vnd.oma.pal+xml application/vnd.oma.poc.detailed-progress-report+xml application/vnd.oma.poc.final-report+xml application/vnd.oma.poc.groups+xml application/vnd.oma.poc.invocation-descriptor+xml application/vnd.oma.poc.optimized-progress-report+xml application/vnd.oma.push application/vnd.oma.scidm.messages+xml application/vnd.oma.xcap-directory+xml application/vnd.oma-scws-config application/vnd.oma-scws-http-request application/vnd.oma-scws-http-response application/vnd.omads-email+xml application/vnd.omads-file+xml application/vnd.omads-folder+xml application/vnd.omaloc-supl-init application/vnd.openeye.oeb oeb application/vnd.openofficeorg.extension oxt application/vnd.openxmlformats-officedocument.custom-properties+xml application/vnd.openxmlformats-officedocument.customXmlProperties+xml application/vnd.openxmlformats-officedocument.drawing+xml application/vnd.openxmlformats-officedocument.drawingml.chart+xml application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml application/vnd.openxmlformats-officedocument.extended-properties+xml application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml application/vnd.openxmlformats-officedocument.presentationml.comments+xml application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml application/vnd.openxmlformats-officedocument.presentationml.presProps+xml application/vnd.openxmlformats-officedocument.presentationml.presentation pptx application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml application/vnd.openxmlformats-officedocument.presentationml.slide sldx application/vnd.openxmlformats-officedocument.presentationml.slide+xml application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml application/vnd.openxmlformats-officedocument.presentationml.tags+xml application/vnd.openxmlformats-officedocument.presentationml.template potx application/vnd.openxmlformats-officedocument.presentationml.template.main+xml application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml application/vnd.openxmlformats-officedocument.theme+xml application/vnd.openxmlformats-officedocument.themeOverride+xml application/vnd.openxmlformats-officedocument.vmlDrawing application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml application/vnd.openxmlformats-officedocument.wordprocessingml.document docx application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml application/vnd.openxmlformats-package.core-properties+xml application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml application/vnd.openxmlformats-package.relationships+xml application/vnd.oracle.resource+json application/vnd.orange.indata application/vnd.osa.netdeploy ndc application/vnd.osgeo.mapguide.package mgp # jar: application/x-java-archive application/vnd.osgi.bundle application/vnd.osgi.dp dp application/vnd.osgi.subsystem esa application/vnd.otps.ct-kip+xml application/vnd.palm prc pdb pqa oprc application/vnd.panoply plp application/vnd.paos+xml application/vnd.pawaafile paw application/vnd.pcos application/vnd.pg.format str application/vnd.pg.osasli ei6 application/vnd.piaccess.application-license pil application/vnd.picsel efif application/vnd.pmi.widget wg application/vnd.poc.group-advertisement+xml application/vnd.pocketlearn plf application/vnd.powerbuilder6 pbd application/vnd.powerbuilder6-s application/vnd.powerbuilder7 application/vnd.powerbuilder7-s application/vnd.powerbuilder75 application/vnd.powerbuilder75-s application/vnd.preminet preminet application/vnd.previewsystems.box box vbox application/vnd.proteus.magazine mgz application/vnd.publishare-delta-tree qps # pti: image/prs.pti application/vnd.pvi.ptid1 ptid application/vnd.pwg-multiplexed application/vnd.pwg-xhtml-print+xml application/vnd.qualcomm.brew-app-res bar application/vnd.Quark.QuarkXPress qxd qxt qwd qwt qxl qxb application/vnd.quobject-quoxdocument quox quiz application/vnd.radisys.moml+xml application/vnd.radisys.msml-audit-conf+xml application/vnd.radisys.msml-audit-conn+xml application/vnd.radisys.msml-audit-dialog+xml application/vnd.radisys.msml-audit-stream+xml application/vnd.radisys.msml-audit+xml application/vnd.radisys.msml-conf+xml application/vnd.radisys.msml-dialog-base+xml application/vnd.radisys.msml-dialog-fax-detect+xml application/vnd.radisys.msml-dialog-fax-sendrecv+xml application/vnd.radisys.msml-dialog-group+xml application/vnd.radisys.msml-dialog-speech+xml application/vnd.radisys.msml-dialog-transform+xml application/vnd.radisys.msml-dialog+xml application/vnd.radisys.msml+xml application/vnd.rainstor.data tree application/vnd.rapid application/vnd.realvnc.bed bed application/vnd.recordare.musicxml mxl application/vnd.recordare.musicxml+xml application/vnd.RenLearn.rlprint application/vnd.rig.cryptonote cryptonote application/vnd.route66.link66+xml link66 # gbr: application/rpki-ghostbusters application/vnd.rs-274x application/vnd.ruckus.download application/vnd.s3sms application/vnd.sailingtracker.track st application/vnd.sbm.cid application/vnd.sbm.mid2 application/vnd.scribus scd sla slaz application/vnd.sealed.3df s3df application/vnd.sealed.csf scsf application/vnd.sealed.doc sdoc sdo s1w application/vnd.sealed.eml seml sem application/vnd.sealed.mht smht smh application/vnd.sealed.net # spp: application/scvp-vp-response application/vnd.sealed.ppt sppt s1p application/vnd.sealed.tiff stif application/vnd.sealed.xls sxls sxl s1e # stm: audio/x-stm application/vnd.sealedmedia.softseal.html stml s1h application/vnd.sealedmedia.softseal.pdf spdf spd s1a application/vnd.seemail see application/vnd.sema sema application/vnd.semd semd application/vnd.semf semf application/vnd.shana.informed.formdata ifm application/vnd.shana.informed.formtemplate itp application/vnd.shana.informed.interchange iif application/vnd.shana.informed.package ipk application/vnd.SimTech-MindMapper twd twds application/vnd.siren+json application/vnd.smaf mmf application/vnd.smart.notebook notebook application/vnd.smart.teacher teacher application/vnd.software602.filler.form+xml fo application/vnd.software602.filler.form-xml-zip zfo application/vnd.solent.sdkm+xml sdkm sdkd application/vnd.spotfire.dxp dxp application/vnd.spotfire.sfs sfs application/vnd.sss-cod application/vnd.sss-dtf application/vnd.sss-ntf application/vnd.stepmania.package smzip application/vnd.stepmania.stepchart sm application/vnd.street-stream application/vnd.sun.wadl+xml wadl application/vnd.sus-calendar sus susp application/vnd.svd application/vnd.swiftview-ics application/vnd.syncml+xml xsm application/vnd.syncml.dm+wbxml bdm application/vnd.syncml.dm+xml xdm application/vnd.syncml.dm.notification application/vnd.syncml.dmddf+wbxml application/vnd.syncml.dmddf+xml ddf application/vnd.syncml.dmtnds+wbxml application/vnd.syncml.dmtnds+xml application/vnd.syncml.ds.notification application/vnd.tao.intent-module-archive tao application/vnd.tcpdump.pcap pcap cap dmp application/vnd.theqvd qvd application/vnd.tmd.mediaflex.api+xml application/vnd.tmobile-livetv tmo application/vnd.trid.tpt tpt application/vnd.triscape.mxs mxs application/vnd.trueapp tra application/vnd.truedoc # cab: application/vnd.ms-cab-compressed application/vnd.ubisoft.webplayer application/vnd.ufdl ufdl ufd frm application/vnd.uiq.theme utz application/vnd.umajin umj application/vnd.unity unityweb application/vnd.uoml+xml uoml uo application/vnd.uplanet.alert application/vnd.uplanet.alert-wbxml application/vnd.uplanet.bearer-choice application/vnd.uplanet.bearer-choice-wbxml application/vnd.uplanet.cacheop application/vnd.uplanet.cacheop-wbxml application/vnd.uplanet.channel application/vnd.uplanet.channel-wbxml application/vnd.uplanet.list application/vnd.uplanet.list-wbxml application/vnd.uplanet.listcmd application/vnd.uplanet.listcmd-wbxml application/vnd.uplanet.signal application/vnd.valve.source.material vmt application/vnd.vcx vcx # sxi: application/vnd.sun.xml.impress application/vnd.vd-study mxi study-inter model-inter # mcd: application/vnd.mcd application/vnd.vectorworks vwx application/vnd.verimatrix.vcas application/vnd.vidsoft.vidconference vsc application/vnd.visio vsd vst vsw vss application/vnd.visionary vis # vsc: application/vnd.vidsoft.vidconference application/vnd.vividence.scriptfile application/vnd.vsf vsf application/vnd.wap.sic sic application/vnd.wap.slc slc application/vnd.wap.wbxml wbxml application/vnd.wap.wmlc wmlc application/vnd.wap.wmlscriptc wmlsc application/vnd.webturbo wtb application/vnd.wfa.p2p p2p application/vnd.wfa.wsc wsc application/vnd.windows.devicepairing application/vnd.wmc wmc application/vnd.wmf.bootstrap # nb: application/mathematica for now application/vnd.wolfram.mathematica application/vnd.wolfram.mathematica.package m application/vnd.wolfram.player nbp application/vnd.wordperfect wpd application/vnd.wqd wqd application/vnd.wrq-hp3000-labelled application/vnd.wt.stf stf application/vnd.wv.csp+xml application/vnd.wv.csp+wbxml wv application/vnd.wv.ssp+xml application/vnd.xacml+json application/vnd.xara xar application/vnd.xfdl xfdl xfd application/vnd.xfdl.webform application/vnd.xmi+xml application/vnd.xmpie.cpkg cpkg application/vnd.xmpie.dpkg dpkg # dpkg: application/vnd.xmpie.dpkg application/vnd.xmpie.plan application/vnd.xmpie.ppkg ppkg application/vnd.xmpie.xlim xlim application/vnd.yamaha.hv-dic hvd application/vnd.yamaha.hv-script hvs application/vnd.yamaha.hv-voice hvp application/vnd.yamaha.openscoreformat osf application/vnd.yamaha.openscoreformat.osfpvg+xml application/vnd.yamaha.remote-setup application/vnd.yamaha.smaf-audio saf application/vnd.yamaha.smaf-phrase spf application/vnd.yamaha.through-ngn application/vnd.yamaha.tunnel-udpencap application/vnd.yaoweme yme application/vnd.yellowriver-custom-menu cmp application/vnd.zul zir zirz application/vnd.zzazz.deck+xml zaz application/voicexml+xml vxml application/vq-rtcp-xr application/watcherinfo+xml wif application/whoispp-query application/whoispp-response application/widget wgt application/wita application/wordperfect5.1 application/wsdl+xml wsdl application/wspolicy+xml wspolicy # yes, this *is* IANA registered despite of x- application/x-www-form-urlencoded application/x400-bp application/xacml+xml application/xcap-att+xml xav application/xcap-caps+xml xca application/xcap-diff+xml xdf application/xcap-el+xml xel application/xcap-error+xml xer application/xcap-ns+xml xns application/xcon-conference-info-diff+xml application/xcon-conference-info+xml application/xenc+xml application/xhtml+xml xhtml xhtm xht # application/xhtml-voice+xml obsoleted by application/xv+xml # xml, xsd, rng: text/xml application/xml # mod: audio/x-mod application/xml-dtd dtd # ent: text/xml-external-parsed-entity application/xml-external-parsed-entity application/xml-patch+xml application/xmpp+xml application/xop+xml xop application/xslt+xml xsl xslt application/xv+xml mxml xhvml xvml xvm application/yang yang application/yin+xml yin application/zip zip application/zlib audio/1d-interleaved-parityfec audio/32kadpcm 726 # 3gp, 3gpp: video/3gpp audio/3gpp # 3g2, 3gpp2: video/3gpp2 audio/3gpp2 audio/ac3 ac3 audio/AMR amr audio/AMR-WB awb audio/amr-wb+ audio/aptx audio/asc acn # aa3, omg: audio/ATRAC3 audio/ATRAC-ADVANCED-LOSSLESS aal # aa3, omg: audio/ATRAC3 audio/ATRAC-X atx audio/ATRAC3 at3 aa3 omg audio/basic au snd audio/BV16 audio/BV32 audio/clearmode audio/CN audio/DAT12 audio/dls dls audio/dsr-es201108 audio/dsr-es202050 audio/dsr-es202211 audio/dsr-es202212 audio/DV audio/DVI4 audio/eac3 audio/encaprtp audio/EVRC evc # qcp: audio/qcelp audio/EVRC-QCP audio/EVRC0 audio/EVRC1 audio/EVRCB evb audio/EVRCB0 audio/EVRCB1 audio/EVRCNW enw audio/EVRCNW0 audio/EVRCNW1 audio/EVRCWB evw audio/EVRCWB0 audio/EVRCWB1 audio/example audio/fwdred audio/G719 audio/G722 audio/G7221 audio/G723 audio/G726-16 audio/G726-24 audio/G726-32 audio/G726-40 audio/G728 audio/G729 audio/G7291 audio/G729D audio/G729E audio/GSM audio/GSM-EFR audio/GSM-HR-08 audio/iLBC lbc audio/ip-mr_v2.5 # wav: audio/x-wav audio/L16 l16 audio/L20 audio/L24 audio/L8 audio/LPC audio/mobile-xmf mxmf # mp4, mpg4: video/mp4, see RFC 4337 audio/mp4 m4a audio/MP4A-LATM audio/MPA audio/mpa-robust audio/mpeg mp3 mpga mp1 mp2 audio/mpeg4-generic audio/ogg oga ogg opus spx audio/parityfec audio/PCMA audio/PCMA-WB audio/PCMU audio/PCMU-WB audio/prs.sid sid psid audio/qcelp qcp audio/raptorfec audio/RED audio/rtp-enc-aescm128 audio/rtp-midi audio/rtploopback audio/rtx audio/SMV smv # qcp: audio/qcelp, see RFC 3625 audio/SMV-QCP audio/SMV0 # mid: audio/midi audio/sp-midi audio/speex audio/t140c audio/t38 audio/telephone-event audio/tone audio/UEMCLIP audio/ulpfec audio/VDVI audio/VMR-WB audio/vnd.3gpp.iufp audio/vnd.4SB audio/vnd.audikoz koz audio/vnd.CELP audio/vnd.cisco.nse audio/vnd.cmles.radio-events audio/vnd.cns.anp1 audio/vnd.cns.inf1 audio/vnd.dece.audio uva uvva audio/vnd.digital-winds eol audio/vnd.dlna.adts audio/vnd.dolby.heaac.1 audio/vnd.dolby.heaac.2 audio/vnd.dolby.mlp mlp audio/vnd.dolby.mps audio/vnd.dolby.pl2 audio/vnd.dolby.pl2x audio/vnd.dolby.pl2z audio/vnd.dolby.pulse.1 audio/vnd.dra # wav: audio/x-wav, cpt: application/mac-compactpro audio/vnd.dts dts audio/vnd.dts.hd dtshd # dvb: video/vnd.dvb.file audio/vnd.dvb.file audio/vnd.everad.plj plj # rm: audio/x-pn-realaudio audio/vnd.hns.audio audio/vnd.lucent.voice lvp audio/vnd.ms-playready.media.pya pya # mxmf: audio/mobile-xmf audio/vnd.nokia.mobile-xmf audio/vnd.nortel.vbk vbk audio/vnd.nuera.ecelp4800 ecelp4800 audio/vnd.nuera.ecelp7470 ecelp7470 audio/vnd.nuera.ecelp9600 ecelp9600 audio/vnd.octel.sbc # audio/vnd.qcelp deprecated in favour of audio/qcelp audio/vnd.rhetorex.32kadpcm audio/vnd.rip rip audio/vnd.sealedmedia.softseal.mpeg smp3 smp s1m audio/vnd.vmx.cvsd audio/vorbis audio/vorbis-config image/cgm cgm image/example image/fits fits fit fts image/g3fax image/gif gif image/ief ief image/jp2 jp2 jpg2 image/jpeg jpg jpeg jpe jfif image/jpm jpm jpgm image/jpx jpx jpf image/ktx ktx image/naplps image/png png image/prs.btif btif btf image/prs.pti pti image/pwg-raster image/svg+xml svg svgz image/t38 t38 image/tiff tiff tif image/tiff-fx tfx image/vnd.adobe.photoshop psd image/vnd.airzip.accelerator.azv azv image/vnd.cns.inf2 image/vnd.dece.graphic uvi uvvi uvg uvvg image/vnd.djvu djvu djv # sub: text/vnd.dvb.subtitle image/vnd.dvb.subtitle image/vnd.dwg dwg image/vnd.dxf dxf image/vnd.fastbidsheet fbs image/vnd.fpx fpx image/vnd.fst fst image/vnd.fujixerox.edmics-mmr mmr image/vnd.fujixerox.edmics-rlc rlc image/vnd.globalgraphics.pgb pgb image/vnd.microsoft.icon ico image/vnd.mix image/vnd.ms-modi mdi image/vnd.net-fpx image/vnd.radiance hdr rgbe xyze image/vnd.sealed.png spng spn s1n image/vnd.sealedmedia.softseal.gif sgif sgi s1g image/vnd.sealedmedia.softseal.jpg sjpg sjp s1j image/vnd.svf image/vnd.tencent.tap tap image/vnd.valve.source.texture vtf image/vnd.wap.wbmp wbmp image/vnd.xiff xif message/CPIM message/delivery-status message/disposition-notification message/example message/external-body message/feedback-report message/global u8msg message/global-delivery-status u8dsn message/global-disposition-notification u8mdn message/global-headers u8hdr message/http # cl: application/simple-filter+xml message/imdn+xml # message/news obsoleted by message/rfc822 message/partial message/rfc822 eml mail art message/s-http message/sip message/sipfrag message/tracking-status message/vnd.si.simp # wsc: application/vnd.wfa.wsc message/vnd.wfa.wsc model/example model/iges igs iges model/mesh msh mesh silo model/vnd.collada+xml dae model/vnd.dwf dwf # 3dml, 3dm: text/vnd.in3d.3dml model/vnd.flatland.3dml model/vnd.gdl gdl gsm win dor lmp rsm msm ism model/vnd.gs-gdl model/vnd.gtw gtw model/vnd.moml+xml moml model/vnd.mts mts model/vnd.opengex ogex model/vnd.parasolid.transmit.binary x_b xmt_bin model/vnd.parasolid.transmit.text x_t xmt_txt model/vnd.valve.source.compiled-map bsp model/vnd.vtu vtu model/vrml wrl vrml # x3db: model/x3d+xml model/x3d+fastinfoset # x3d: application/vnd.hzn-3d-crossword model/x3d+xml x3db model/x3d-vrml x3dv x3dvz multipart/alternative multipart/appledouble multipart/byteranges multipart/digest multipart/encrypted multipart/form-data multipart/header-set multipart/mixed multipart/parallel multipart/related multipart/report multipart/signed multipart/voice-message vpm multipart/x-mixed-replace text/1d-interleaved-parityfec text/cache-manifest appcache manifest text/calendar ics ifb text/css css text/csv csv text/csv-schema csvs text/directory text/dns soa zone text/encaprtp # text/ecmascript obsoleted by application/ecmascript text/enriched text/example text/fwdred text/grammar-ref-list text/html html htm # text/javascript obsoleted by application/javascript text/jcr-cnd cnd text/markdown markdown md text/mizar miz text/n3 n3 text/parameters text/parityfec text/plain txt asc text pm el c h cc hh cxx hxx f90 conf log text/provenance-notation provn text/prs.fallenstein.rst rst text/prs.lines.tag tag dsc text/raptorfec text/RED text/rfc822-headers text/richtext rtx # rtf: application/rtf text/rtf text/rtp-enc-aescm128 text/rtploopback text/rtx text/sgml sgml sgm text/t140 text/tab-separated-values tsv text/troff t tr roff text/turtle ttl text/ulpfec text/uri-list uris uri text/vcard vcf vcard text/vnd.a a text/vnd.abc abc # curl: application/vnd.curl text/vnd.curl text/vnd.debian.copyright copyright text/vnd.DMClientScript dms text/vnd.dvb.subtitle sub text/vnd.esmertec.theme-descriptor jtd text/vnd.fly fly text/vnd.fmi.flexstor flx text/vnd.graphviz gv dot text/vnd.in3d.3dml 3dml 3dm text/vnd.in3d.spot spot spo text/vnd.IPTC.NewsML text/vnd.IPTC.NITF text/vnd.latex-z text/vnd.motorola.reflex text/vnd.ms-mediapackage mpf text/vnd.net2phone.commcenter.command ccc text/vnd.radisys.msml-basic-layout text/vnd.si.uricatalogue uric text/vnd.sun.j2me.app-descriptor jad text/vnd.trolltech.linguist ts text/vnd.wap.si si text/vnd.wap.sl sl text/vnd.wap.wml wml text/vnd.wap.wmlscript wmls text/xml xml xsd rng text/xml-external-parsed-entity ent video/1d-interleaved-parityfec video/3gpp 3gp 3gpp video/3gpp2 3g2 3gpp2 video/3gpp-tt video/BMPEG video/BT656 video/CelB video/DV video/encaprtp video/example video/H261 video/H263 video/H263-1998 video/H263-2000 video/H264 video/H264-RCDO video/H264-SVC video/iso.segment m4s video/JPEG video/jpeg2000 video/mj2 mj2 mjp2 video/MP1S video/MP2P video/MP2T video/mp4 mp4 mpg4 m4v video/MP4V-ES video/mpeg mpeg mpg mpe m1v m2v video/mpeg4-generic video/MPV video/nv video/ogg ogv video/parityfec video/pointer video/quicktime mov qt video/raptorfec video/raw video/rtp-enc-aescm128 video/rtploopback video/rtx video/SMPTE292M video/ulpfec video/vc1 video/vnd.CCTV video/vnd.dece.hd uvh uvvh video/vnd.dece.mobile uvm uvvm video/vnd.dece.mp4 uvu uvvu video/vnd.dece.pd uvp uvvp video/vnd.dece.sd uvs uvvs video/vnd.dece.video uvv uvvv video/vnd.directv.mpeg video/vnd.directv.mpeg-tts video/vnd.dlna.mpeg-tts video/vnd.dvb.file dvb video/vnd.fvt fvt # rm: audio/x-pn-realaudio video/vnd.hns.video video/vnd.iptvforum.1dparityfec-1010 video/vnd.iptvforum.1dparityfec-2005 video/vnd.iptvforum.2dparityfec-1010 video/vnd.iptvforum.2dparityfec-2005 video/vnd.iptvforum.ttsavc video/vnd.iptvforum.ttsmpeg2 video/vnd.motorola.video video/vnd.motorola.videop video/vnd.mpegurl mxu m4u video/vnd.ms-playready.media.pyv pyv video/vnd.nokia.interleaved-multimedia nim video/vnd.nokia.videovoip # mp4: video/mp4 video/vnd.objectvideo video/vnd.radgamettools.bink bik bk2 video/vnd.radgamettools.smacker smk video/vnd.sealed.mpeg1 smpg s11 # smpg: video/vnd.sealed.mpeg1 video/vnd.sealed.mpeg4 s14 video/vnd.sealed.swf sswf ssw video/vnd.sealedmedia.softseal.mov smov smo s1q # uvu, uvvu: video/vnd.dece.mp4 video/vnd.uvvu.mp4 video/vnd.vivo viv # Non-IANA types application/mac-compactpro cpt application/metalink+xml metalink application/owl+xml owx application/rss+xml rss application/vnd.android.package-archive apk application/vnd.oma.dd+xml dd application/vnd.oma.drm.content dcf # odf: application/vnd.oasis.opendocument.formula application/vnd.oma.drm.dcf o4a o4v application/vnd.oma.drm.message dm application/vnd.oma.drm.rights+wbxml drc application/vnd.oma.drm.rights+xml dr application/vnd.sun.xml.calc sxc application/vnd.sun.xml.calc.template stc application/vnd.sun.xml.draw sxd application/vnd.sun.xml.draw.template std application/vnd.sun.xml.impress sxi application/vnd.sun.xml.impress.template sti application/vnd.sun.xml.math sxm application/vnd.sun.xml.writer sxw application/vnd.sun.xml.writer.global sxg application/vnd.sun.xml.writer.template stw application/vnd.symbian.install sis application/vnd.wap.mms-message mms application/x-annodex anx application/x-bcpio bcpio application/x-bittorrent torrent application/x-bzip2 bz2 application/x-cdlink vcd application/x-chess-pgn pgn application/x-chrome-extension crx application/x-cpio cpio application/x-csh csh application/x-director dcr dir dxr application/x-dvi dvi application/x-futuresplash spl application/x-gtar gtar application/x-hdf hdf application/x-java-archive jar application/x-java-jnlp-file jnlp application/x-java-pack200 pack application/x-killustrator kil application/x-latex latex application/x-netcdf nc cdf application/x-perl pl application/x-rpm rpm application/x-sh sh application/x-shar shar application/x-shockwave-flash swf application/x-stuffit sit application/x-sv4cpio sv4cpio application/x-sv4crc sv4crc application/x-tar tar application/x-tcl tcl application/x-tex tex application/x-texinfo texinfo texi application/x-troff-man man 1 2 3 4 5 6 7 8 application/x-troff-me me application/x-troff-ms ms application/x-ustar ustar application/x-wais-source src application/x-xpinstall xpi application/x-xspf+xml xspf application/x-xz xz audio/midi mid midi kar audio/x-aiff aif aiff aifc audio/x-annodex axa audio/x-flac flac audio/x-matroska mka audio/x-mod mod ult uni m15 mtm 669 med audio/x-mpegurl m3u audio/x-ms-wax wax audio/x-ms-wma wma audio/x-pn-realaudio ram rm audio/x-realaudio ra audio/x-s3m s3m audio/x-stm stm audio/x-wav wav chemical/x-xyz xyz image/bmp bmp image/webp webp image/x-cmu-raster ras image/x-portable-anymap pnm image/x-portable-bitmap pbm image/x-portable-graymap pgm image/x-portable-pixmap ppm image/x-rgb rgb image/x-targa tga image/x-xbitmap xbm image/x-xpixmap xpm image/x-xwindowdump xwd text/html-sandboxed sandboxed text/x-pod pod text/x-setext etx video/webm webm video/x-annodex axv video/x-flv flv video/x-javafx fxm video/x-matroska mkv video/x-matroska-3d mk3d video/x-ms-asf asx video/x-ms-wm wm video/x-ms-wmv wmv video/x-ms-wmx wmx video/x-ms-wvx wvx video/x-msvideo avi video/x-sgi-movie movie x-conference/x-cooltalk ice x-epoc/x-sisx-app sisx |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
defmodule Plug.Parsers do
defmodule RequestTooLargeError do
@moduledoc """
Error raised when the request is too large.
"""
defexception message: "the request is too large. If you are willing to process " <>
"larger requests, please give a :length to Plug.Parsers",
plug_status: 413
end
defmodule UnsupportedMediaTypeError do
@moduledoc """
Error raised when the request body cannot be parsed.
"""
defexception media_type: nil, plug_status: 415
def message(exception) do
"unsupported media type #{exception.media_type}"
end
end
defmodule BadEncodingError do
@moduledoc """
Raised when the request body contains bad encoding.
"""
defexception message: nil, plug_status: 415
end
defmodule ParseError do
@moduledoc """
Error raised when the request body is malformed.
"""
defexception exception: nil, plug_status: 400
def message(exception) do
exception = exception.exception
"malformed request, got #{inspect exception.__struct__} " <>
"with message #{Exception.message(exception)}"
end
end
@moduledoc """
A plug for parsing the request body.
This module also specifies a behaviour that all the parsers to be used with
Plug should adopt.
## Options
* `:parsers` - a set of modules to be invoked for parsing.
These modules need to implement the behaviour outlined in
this module.
* `:pass` - an optional list of MIME type strings that are allowed
to pass through. Any mime not handled by a parser and not explicitly
listed in `:pass` will `raise UnsupportedMediaTypeError`. For example:
* `["*/*"]` - never raises
* `["text/html", "application/*"]` - doesn't raise for those values
* `[]` - always raises (default)
All options supported by `Plug.Conn.read_body/2` are also supported here (for
example the `:length` option which specifies the max body length to read).
## Examples
plug Plug.Parsers, parsers: [:urlencoded, :multipart]
plug Plug.Parsers, parsers: [:urlencoded, :json],
pass: ["text/*"],
json_decoder: Poison
## Built-in parsers
Plug ships with the following parsers:
* `Plug.Parsers.URLENCODED` - parses `application/x-www-form-urlencoded`
requests
* `Plug.Parsers.MULTIPART` - parses `multipart/form-data` and
`multipart/mixed` requests
* `Plug.Parsers.JSON` - parses `application/json` requests with the given
`:json_decoder`
This plug will raise `Plug.Parsers.UnsupportedMediaTypeError` by default if
the request cannot be parsed by any of the given types and the MIME type has
not been explicity accepted with the `:accept` option.
`Plug.Parsers.RequestTooLargeError` will be raised if the request goes over
the given limit.
Parsers may raise a `Plug.Parsers.ParseError` if the request has a malformed
body.
## File handling
If a file is uploaded via any of the parsers, Plug will
stream the uploaded contents to a file in a temporary directory in order to
avoid loading the whole file into memory. For such, the `:plug` application
needs to be started in order for file uploads to work. More details on how the
uploaded file is handled can be found in the documentation for `Plug.Upload`.
When a file is uploaded, the request parameter that identifies that file will
be a `Plug.Upload` struct with information about the uploaded file (e.g.
filename and content type) and about where the file is stored.
The temporary directory where files are streamed to can be customized by
setting the `PLUG_TMPDIR` environment variable on the host system. If
`PLUG_TMPDIR` isn't set, Plug will look at some environment
variables which usually hold the value of the system's temporary directory
(like `TMPDIR` or `TMP`). If no value is found in any of those variables,
`/tmp` is used as a default.
"""
alias Plug.Conn
use Behaviour
@doc """
Attempts to parse the connection's request body given the content-type type
and subtype and the headers. Returns:
* `{:ok, conn}` if the parser is able to handle the given content-type
* `{:next, conn}` if the next parser should be invoked
* `{:error, :too_large, conn}` if the request goes over the given limit
"""
defcallback parse(Conn.t, type :: binary, subtype :: binary,
headers :: Keyword.t, opts :: Keyword.t) ::
{:ok, Conn.params, Conn.t} |
{:error, :too_large, Conn.t} |
{:next, Conn.t}
@behaviour Plug
@methods ~w(POST PUT PATCH DELETE)
def init(opts) do
parsers = Keyword.get(opts, :parsers) || raise_missing_parsers
opts
|> Keyword.put(:parsers, convert_parsers(parsers))
|> Keyword.put_new(:length, 8_000_000)
|> Keyword.put_new(:pass, [])
end
defp raise_missing_parsers do
raise ArgumentError, "Plug.Parsers expects a set of parsers to be given in :parsers"
end
defp convert_parsers(parsers) do
for parser <- parsers do
case Atom.to_string(parser) do
"Elixir." <> _ -> parser
reference -> Module.concat(Plug.Parsers, String.upcase(reference))
end
end
end
def call(%Conn{req_headers: req_headers, method: method,
body_params: %Plug.Conn.Unfetched{}} = conn, opts) when method in @methods do
conn = Conn.fetch_query_params(conn)
case List.keyfind(req_headers, "content-type", 0) do
{"content-type", ct} ->
case Conn.Utils.content_type(ct) do
{:ok, type, subtype, headers} ->
reduce(conn, Keyword.fetch!(opts, :parsers), type, subtype, headers, opts)
:error ->
%{conn | body_params: %{}}
end
nil ->
%{conn | body_params: %{}}
end
end
def call(%Conn{body_params: %Plug.Conn.Unfetched{}} = conn, _opts) do
conn = Conn.fetch_query_params(conn)
%{conn | body_params: %{}}
end
def call(%Conn{} = conn, _opts) do
Conn.fetch_query_params(conn)
end
defp reduce(conn, [h|t], type, subtype, headers, opts) do
case h.parse(conn, type, subtype, headers, opts) do
{:ok, body, %Conn{params: %Plug.Conn.Unfetched{}, query_params: query} = conn} ->
%{conn | body_params: body, params: query |> Map.merge(body)}
{:ok, body, %Conn{params: params, query_params: query} = conn} ->
%{conn | body_params: body, params: params |> Map.merge(query) |> Map.merge(body)}
{:next, conn} ->
reduce(conn, t, type, subtype, headers, opts)
{:error, :too_large, _conn} ->
raise RequestTooLargeError
end
end
defp reduce(conn, [], type, subtype, _headers, opts) do
ensure_accepted_mimes(conn, type, subtype, Keyword.fetch!(opts, :pass))
end
defp ensure_accepted_mimes(conn, _type, _subtype, ["*/*"]), do: conn
defp ensure_accepted_mimes(conn, type, subtype, pass) do
if "#{type}/#{subtype}" in pass || "#{type}/*" in pass do
%{conn | body_params: %{}}
else
raise UnsupportedMediaTypeError, media_type: "#{type}/#{subtype}"
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
defmodule Plug.Parsers.JSON do
@moduledoc """
Parses JSON request body.
JSON arrays are parsed into a `"_json"` key to allow
proper param merging.
An empty request body is parsed as an empty map.
"""
@behaviour Plug.Parsers
import Plug.Conn
def parse(conn, "application", subtype, _headers, opts) do
if subtype == "json" || String.ends_with?(subtype, "+json") do
decoder = Keyword.get(opts, :json_decoder) ||
raise ArgumentError, "JSON parser expects a :json_decoder option"
conn
|> read_body(opts)
|> decode(decoder)
else
{:next, conn}
end
end
def parse(conn, _type, _subtype, _headers, _opts) do
{:next, conn}
end
defp decode({:more, _, conn}, _decoder) do
{:error, :too_large, conn}
end
defp decode({:ok, "", conn}, _decoder) do
{:ok, %{}, conn}
end
defp decode({:ok, body, conn}, decoder) do
case decoder.decode!(body) do
terms when is_map(terms) ->
{:ok, terms, conn}
terms ->
{:ok, %{"_json" => terms}, conn}
end
rescue
e -> raise Plug.Parsers.ParseError, exception: e
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
defmodule Plug.Parsers.MULTIPART do
@moduledoc """
Parses multipart request body.
"""
@behaviour Plug.Parsers
def parse(conn, "multipart", subtype, _headers, opts) when subtype in ["form-data", "mixed"] do
{adapter, state} = conn.adapter
try do
adapter.parse_req_multipart(state, opts, &handle_headers/1)
rescue
e -> raise Plug.Parsers.ParseError, exception: e
else
{:ok, params, state} ->
{:ok, params, %{conn | adapter: {adapter, state}}}
{:more, _params, state} ->
{:error, :too_large, %{conn | adapter: {adapter, state}}}
end
end
def parse(conn, _type, _subtype, _headers, _opts) do
{:next, conn}
end
defp handle_headers(headers) do
case List.keyfind(headers, "content-disposition", 0) do
{_, disposition} -> handle_disposition(disposition, headers)
nil -> :skip
end
end
defp handle_disposition(disposition, headers) do
case :binary.split(disposition, ";") do
[_, params] ->
params = Plug.Conn.Utils.params(params)
if name = Map.get(params, "name") do
handle_disposition_params(name, params, headers)
else
:skip
end
[_] ->
:skip
end
end
defp handle_disposition_params(name, params, headers) do
case Map.get(params, "filename") do
nil -> {:binary, name}
"" -> :skip
filename ->
path = Plug.Upload.random_file!("multipart")
{:file, name, path, %Plug.Upload{filename: filename, path: path,
content_type: get_header(headers, "content-type")}}
end
end
defp get_header(headers, key) do
case List.keyfind(headers, key, 0) do
{^key, value} -> value
nil -> nil
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
defmodule Plug.Parsers.URLENCODED do
@moduledoc """
Parses urlencoded request body.
"""
@behaviour Plug.Parsers
alias Plug.Conn
def parse(conn, "application", "x-www-form-urlencoded", _headers, opts) do
case Conn.read_body(conn, opts) do
{:ok, body, conn} ->
Plug.Conn.Utils.validate_utf8!(body, "urlencoded body")
{:ok, Plug.Conn.Query.decode(body), conn}
{:more, _data, conn} ->
{:error, :too_large, conn}
end
end
def parse(conn, _type, _subtype, _headers, _opts) do
{:next, conn}
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
defmodule Plug.RequestId do
@moduledoc """
A plug for generating a unique request id for each request. A generated
request id will in the format "uq8hs30oafhj5vve8ji5pmp7mtopc08f".
If a request id already exists as the "x-request-id" HTTP request header,
then that value will be used assuming it is between 20 and 200 characters.
If it is not, a new request id will be generated.
The request id is added to the Logger metadata as `:request_id` and the response as
the "x-request-id" HTTP header. To see the request id in your log output,
configure your logger backends to include the `:request_id` metadata:
config :logger, :console, metadata: [:request_id]
It is recommended to include this metadata configuration in your production
configuration file.
To use it, just plug it into the desired module:
plug Plug.RequestId
## Options
* `:http_header` - The name of the HTTP *request* header to check for
existing request ids. This is also the HTTP *response* header that will be
set with the request id. Default value is "x-request-id"
plug Plug.RequestId, http_header: "custom-request-id"
"""
require Logger
alias Plug.Conn
@behaviour Plug
def init(opts) do
Keyword.get(opts, :http_header, "x-request-id")
end
def call(conn, req_id_header) do
conn
|> get_request_id(req_id_header)
|> set_request_id(req_id_header)
end
defp get_request_id(conn, header) do
case Conn.get_req_header(conn, header) do
[] -> {conn, generate_request_id}
[val|_] -> if valid_request_id?(val), do: {conn, val}, else: {conn, generate_request_id}
end
end
defp set_request_id({conn, request_id}, header) do
Logger.metadata(request_id: request_id)
conn |> Conn.put_resp_header(header, request_id)
end
defp generate_request_id do
:crypto.rand_bytes(20) |> Base.hex_encode32(case: :lower)
end
defp valid_request_id?(s), do: byte_size(s) in 20..200
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
defmodule Plug.Router do
@moduledoc ~S"""
A DSL to define a routing algorithm that works with Plug.
It provides a set of macros to generate routes. For example:
defmodule AppRouter do
use Plug.Router
plug :match
plug :dispatch
get "/hello" do
send_resp(conn, 200, "world")
end
match _ do
send_resp(conn, 404, "oops")
end
end
Each route needs to return a connection, as per the Plug spec.
A catch-all `match` is recommended to be defined as in the example
above, otherwise routing fails with a function clause error.
The router is itself a plug, which means it can be invoked as:
AppRouter.call(conn, AppRouter.init([]))
Notice the router contains a plug pipeline and by default it requires
two plugs: `match` and `dispatch`. `match` is responsible for
finding a matching route which is then forwarded to `dispatch`.
This means users can easily hook into the router mechanism and add
behaviour before match, before dispatch or after both.
To specify private options on `match` that can be used by plugs
before `dispatch` pass an option with key `:private` containing a map.
Example:
get "/hello", private: %{an_option: :a_value} do
send_resp(conn, 200, "world")
end
These options are assigned to `:private` in the call's `Plug.Conn`.
## Routes
get "/hello" do
send_resp(conn, 200, "world")
end
In the example above, a request will only match if it is a `GET` request and
the route is "/hello". The supported HTTP methods are `get`, `post`, `put`,
`patch`, `delete` and `options`.
A route can also specify parameters which will then be
available in the function body:
get "/hello/:name" do
send_resp(conn, 200, "hello #{name}")
end
Routes allow for globbing which will match the remaining parts
of a route and can be available as a parameter in the function
body. Also note that a glob can't be followed by other segments:
get "/hello/*_rest" do
send_resp(conn, 200, "matches all routes starting with /hello")
end
get "/hello/*glob" do
send_resp(conn, 200, "route after /hello: #{inspect glob}")
end
Finally, a general `match` function is also supported:
match "/hello" do
send_resp(conn, 200, "world")
end
A `match` will match any route regardless of the HTTP method.
Check `match/3` for more information on how route compilation
works and a list of supported options.
## Error handling
In case something goes wrong in a request, the router by default
will crash, without returning any response to the client. This
behaviour can be configured in two ways, by using two different
modules:
* `Plug.ErrorHandler` - allows the developer to customize exactly
which page is sent to the client via the `handle_errors/2` function;
* `Plug.Debugger` - automatically shows debugging and request information
about the failure. This module is recommended to be used only in a
development environment.
Here is an example of how both modules could be used in an application:
defmodule AppRouter do
use Plug.Router
if Mix.env == :dev do
use Plug.Debugger
end
use Plug.ErrorHandler
plug :match
plug :dispatch
get "/hello" do
send_resp(conn, 200, "world")
end
defp handle_errors(conn, %{kind: _kind, reason: _reason, stack: _stack}) do
send_resp(conn, conn.status, "Something went wrong")
end
end
## Routes compilation
All routes are compiled to a match function that receives
three arguments: the method, the request path split on `/`
and the connection. Consider this example:
match "/foo/bar", via: :get do
send_resp(conn, 200, "hello world")
end
It is compiled to:
defp match("GET", ["foo", "bar"], conn) do
send_resp(conn, 200, "hello world")
end
This opens up a few possibilities. First, guards can be given
to `match`:
match "/foo/:bar" when size(bar) <= 3, via: :get do
send_resp(conn, 200, "hello world")
end
Second, a list of split paths (which is the compiled result) is
also allowed:
match ["foo", bar], via: :get do
send_resp(conn, 200, "hello world")
end
After a match is found, the block given as `do/end` is stored
as a function in the connection. This function is then retrieved
and invoked in the `dispatch` plug.
## Options
When used, the following options are accepted by `Plug.Router`:
* `:log_on_halt` - accepts the level to log whenever the request is halted
"""
@doc false
defmacro __using__(opts) do
quote location: :keep do
import Plug.Router
@before_compile Plug.Router
use Plug.Builder, unquote(opts)
defp match(conn, _opts) do
do_match(conn, conn.method, Enum.map(conn.path_info, &URI.decode/1), conn.host)
end
defp dispatch(%Plug.Conn{assigns: assigns} = conn, _opts) do
Map.get(conn.private, :plug_route).(conn)
end
defoverridable [match: 2, dispatch: 2]
end
end
@doc false
defmacro __before_compile__(_env) do
quote do
import Plug.Router, only: []
end
end
## Match
@doc """
Main API to define routes.
It accepts an expression representing the path and many options
allowing the match to be configured.
## Examples
match "/foo/bar", via: :get do
send_resp(conn, 200, "hello world")
end
## Options
`match/3` and the other route macros accept the following options:
* `:host` - the host which the route should match. Defaults to `nil`,
meaning no host match, but can be a string like "example.com" or a
string ending with ".", like "subdomain." for a subdomain match.
* `:via` - matches the route against some specific HTTP method (specified as
an atom, like `:get` or `:put`.
* `:do` - contains the implementation to be invoked in case
the route matches.
"""
defmacro match(path, options, contents \\ []) do
compile(nil, path, options, contents)
end
@doc """
Dispatches to the path only if the request is a GET request.
See `match/3` for more examples.
"""
defmacro get(path, options, contents \\ []) do
compile(:get, path, options, contents)
end
@doc """
Dispatches to the path only if the request is a POST request.
See `match/3` for more examples.
"""
defmacro post(path, options, contents \\ []) do
compile(:post, path, options, contents)
end
@doc """
Dispatches to the path only if the request is a PUT request.
See `match/3` for more examples.
"""
defmacro put(path, options, contents \\ []) do
compile(:put, path, options, contents)
end
@doc """
Dispatches to the path only if the request is a PATCH request.
See `match/3` for more examples.
"""
defmacro patch(path, options, contents \\ []) do
compile(:patch, path, options, contents)
end
@doc """
Dispatches to the path only if the request is a DELETE request.
See `match/3` for more examples.
"""
defmacro delete(path, options, contents \\ []) do
compile(:delete, path, options, contents)
end
@doc """
Dispatches to the path only if the request is an OPTIONS request.
See `match/3` for more examples.
"""
defmacro options(path, options, contents \\ []) do
compile(:options, path, options, contents)
end
@doc """
Forwards requests to another Plug. The `path_info` of the forwarded
connection will exclude the portion of the path specified in the
call to `forward`.
## Options
`forward` accepts the following options:
* `:to` - a Plug the requests will be forwarded to.
* `:host` - a string representing the host or subdomain, exactly like in
`match/3`.
All remaining options are passed to the target plug.
## Examples
forward "/users", to: UserRouter
Assuming the above code, a request to `/users/sign_in` will be forwarded to
the `UserRouter` plug, which will receive what it will see as a request to
`/sign_in`.
Some other examples:
forward "/foo/bar", to: :foo_bar_plug, host: "foobar."
forward "/api", to: ApiRouter, plug_specific_option: true
"""
defmacro forward(path, options) when is_binary(path) do
quote bind_quoted: [path: path, options: options] do
{target, options} = Keyword.pop(options, :to)
{options, plug_options} = Keyword.split(options, [:host, :private])
if is_nil(target) or !is_atom(target) do
raise ArgumentError, message: "expected :to to be an alias or an atom"
end
@plug_forward_target target
@plug_forward_opts target.init(plug_options)
# Delegate the matching to the match/3 macro along with the options
# specified by Keyword.split/2.
match path <> "/*glob", options do
Plug.Router.Utils.forward(
var!(conn),
var!(glob),
@plug_forward_target,
@plug_forward_opts
)
end
end
end
## Match Helpers
@doc false
def __route__(method, path, guards, options) do
{method, guards} = build_methods(List.wrap(method || options[:via]), guards)
{_vars, match} = Plug.Router.Utils.build_path_match(path)
private = extract_private_merger(options)
host_match = Plug.Router.Utils.build_host_match(options[:host])
{quote(do: conn), method, match, host_match, guards, private}
end
# Entry point for both forward and match that is actually
# responsible to compile the route.
defp compile(method, expr, options, contents) do
{body, options} =
cond do
b = contents[:do] ->
{b, options}
options[:do] ->
Keyword.pop(options, :do)
true ->
raise ArgumentError, message: "expected :do to be given as option"
end
{path, guards} = extract_path_and_guards(expr)
quote bind_quoted: [method: method,
path: path,
options: options,
guards: Macro.escape(guards, unquote: true),
body: Macro.escape(body, unquote: true)] do
route = Plug.Router.__route__(method, path, guards, options)
{conn, method, match, host, guards, private} = route
defp do_match(unquote(conn), unquote(method), unquote(match), unquote(host)) when unquote(guards) do
unquote(private)
Plug.Conn.put_private(unquote(conn), :plug_route, fn var!(conn) -> unquote(body) end)
end
end
end
defp extract_private_merger(options) when is_list(options) do
if private = Keyword.get(options, :private) do
quote do
conn = update_in conn.private, &Map.merge(&1, unquote(Macro.escape(private)))
end
end
end
# Convert the verbs given with `:via` into a variable and guard set that can
# be added to the dispatch clause.
defp build_methods([], guards) do
{quote(do: _), guards}
end
defp build_methods([method], guards) do
{Plug.Router.Utils.normalize_method(method), guards}
end
defp build_methods(methods, guards) do
methods = Enum.map methods, &Plug.Router.Utils.normalize_method(&1)
var = quote do: method
guards = join_guards(quote(do: unquote(var) in unquote(methods)), guards)
{var, guards}
end
defp join_guards(fst, true), do: fst
defp join_guards(fst, snd), do: (quote do: unquote(fst) and unquote(snd))
# Extract the path and guards from the path.
defp extract_path_and_guards({:when, _, [path, guards]}), do: {extract_path(path), guards}
defp extract_path_and_guards(path), do: {extract_path(path), true}
defp extract_path({:_, _, var}) when is_atom(var), do: "/*_path"
defp extract_path(path), do: path
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
defmodule Plug.Router.InvalidSpecError do
defexception message: "invalid route specification"
end
defmodule Plug.Router.Utils do
@moduledoc false
@doc """
Converts a given method to its connection representation.
The request method is stored in the `Plug.Conn` struct as an uppercase string
(like `"GET"` or `"POST"`). This function converts `method` to that
representation.
## Examples
iex> Plug.Router.Utils.normalize_method(:get)
"GET"
"""
def normalize_method(method) do
method |> to_string |> String.upcase
end
@doc ~S"""
Builds the pattern that will be used to match against the request's host
(provided via the `:host`) option.
If `host` is `nil`, a wildcard match (`_`) will be returned. If `host` ends
with a dot, a match like `"host." <> _` will be returned.
## Examples
iex> Plug.Router.Utils.build_host_match(nil)
{:_, [], Plug.Router.Utils}
iex> Plug.Router.Utils.build_host_match("foo.com")
"foo.com"
iex> Plug.Router.Utils.build_host_match("api.") |> Macro.to_string
"\"api.\" <> _"
"""
def build_host_match(host) do
cond do
is_nil host -> quote do: _
String.last(host) == "." -> quote do: unquote(host) <> _
is_binary host -> host
end
end
@doc """
Generates a representation that will only match routes
according to the given `spec`.
If a non-binary spec is given, it is assumed to be
custom match arguments and they are simply returned.
## Examples
iex> Plug.Router.Utils.build_path_match("/foo/:id")
{[:id], ["foo", {:id, [], nil}]}
"""
def build_path_match(spec, context \\ nil) when is_binary(spec) do
build_path_match split(spec), context, [], []
end
@doc """
Forwards requests to another Plug at a new path.
"""
def forward(%Plug.Conn{path_info: path, script_name: script} = conn, new_path, target, opts) do
{base, ^new_path} = Enum.split(path, length(path) - length(new_path))
conn = %{conn | path_info: new_path, script_name: script ++ base} |> target.call(opts)
%{conn | path_info: path, script_name: script}
end
@doc """
Splits the given path into several segments.
It ignores both leading and trailing slashes in the path.
## Examples
iex> Plug.Router.Utils.split("/foo/bar")
["foo", "bar"]
iex> Plug.Router.Utils.split("/:id/*")
[":id", "*"]
iex> Plug.Router.Utils.split("/foo//*_bar")
["foo", "*_bar"]
"""
def split(bin) do
for segment <- String.split(bin, "/"), segment != "", do: segment
end
## Helpers
# Loops each segment checking for matches.
defp build_path_match([h|t], context, vars, acc) do
handle_segment_match segment_match(h, "", context), t, context, vars, acc
end
defp build_path_match([], _context, vars, acc) do
{vars |> Enum.uniq |> Enum.reverse, Enum.reverse(acc)}
end
# Handle each segment match. They can either be a
# :literal ("foo"), an :identifier (":bar") or a :glob ("*path")
defp handle_segment_match({:literal, literal}, t, context, vars, acc) do
build_path_match t, context, vars, [literal|acc]
end
defp handle_segment_match({:identifier, identifier, expr}, t, context, vars, acc) do
build_path_match t, context, [identifier|vars], [expr|acc]
end
defp handle_segment_match({:glob, _identifier, _expr}, t, _context, _vars, _acc) when t != [] do
raise Plug.Router.InvalidSpecError,
message: "cannot have a *glob followed by other segments"
end
defp handle_segment_match({:glob, identifier, expr}, _t, context, vars, [hs|ts]) do
acc = [{:|, [], [hs, expr]} | ts]
build_path_match([], context, [identifier|vars], acc)
end
defp handle_segment_match({:glob, identifier, expr}, _t, context, vars, _) do
{vars, expr} = build_path_match([], context, [identifier|vars], [expr])
{vars, hd(expr)}
end
# In a given segment, checks if there is a match.
defp segment_match(":" <> argument, buffer, context) do
identifier = binary_to_identifier(":", argument)
expr = quote_if_buffer identifier, buffer, context, fn var ->
quote do: unquote(buffer) <> unquote(var)
end
{:identifier, identifier, expr}
end
defp segment_match("*" <> argument, buffer, context) do
underscore = {:_, [], context}
identifier = binary_to_identifier("*", argument)
expr = quote_if_buffer identifier, buffer, context, fn var ->
quote do: [unquote(buffer) <> unquote(underscore)|unquote(underscore)] = unquote(var)
end
{:glob, identifier, expr}
end
defp segment_match(<<h, t::binary>>, buffer, context) do
segment_match t, buffer <> <<h>>, context
end
defp segment_match(<<>>, buffer, _context) do
{:literal, buffer}
end
defp quote_if_buffer(identifier, "", context, _fun) do
{identifier, [], context}
end
defp quote_if_buffer(identifier, _buffer, context, fun) do
fun.({identifier, [], context})
end
defp binary_to_identifier(prefix, <<letter, _::binary>> = binary)
when letter in ?a..?z or letter == ?_ do
if binary =~ ~r/^\w+$/ do
String.to_atom(binary)
else
raise Plug.Router.InvalidSpecError,
message: "#{prefix}identifier in routes must be made of letters, numbers and underscore"
end
end
defp binary_to_identifier(prefix, _) do
raise Plug.Router.InvalidSpecError,
message: "#{prefix} in routes must be followed by lowercase letters or underscore"
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
defmodule Plug.Session do
@moduledoc """
A plug to handle session cookies and session stores.
The session is accessed via functions on `Plug.Conn`. Cookies and
session have to be fetched with `Plug.Conn.fetch_session/1` before the
session can be accessed.
Consider using `Plug.CSRFProtection` when using `Plug.Session`.
## Session stores
See `Plug.Session.Store` for the specification session stores are required to
implement.
Plug ships with the following session stores:
* `Plug.Session.ETS`
* `Plug.Session.COOKIE`
## Options
* `:store` - session store module (required);
* `:key` - session cookie key (required);
* `:domain` - see `Plug.Conn.put_resp_cookie/4`;
* `:max_age` - see `Plug.Conn.put_resp_cookie/4`;
* `:path` - see `Plug.Conn.put_resp_cookie/4`;
* `:secure` - see `Plug.Conn.put_resp_cookie/4`;
Additional options can be given to the session store, see the store's
documentation for the options it accepts.
## Examples
plug Plug.Session, store: :ets, key: "_my_app_session", table: :session
"""
alias Plug.Conn
@behaviour Plug
@cookie_opts [:domain, :max_age, :path, :secure]
def init(opts) do
store = Keyword.fetch!(opts, :store) |> convert_store
key = Keyword.fetch!(opts, :key)
cookie_opts = Keyword.take(opts, @cookie_opts)
store_opts = Keyword.drop(opts, [:store, :key] ++ @cookie_opts)
store_config = store.init(store_opts)
%{store: store,
store_config: store_config,
key: key,
cookie_opts: cookie_opts}
end
def call(conn, config) do
Conn.put_private(conn, :plug_session_fetch, fetch_session(config))
end
defp convert_store(store) do
case Atom.to_string(store) do
"Elixir." <> _ -> store
reference -> Module.concat(Plug.Session, String.upcase(reference))
end
end
defp fetch_session(config) do
%{store: store, store_config: store_config, key: key} = config
fn conn ->
{sid, session} =
if cookie = conn.cookies[key] do
store.get(conn, cookie, store_config)
else
{nil, %{}}
end
conn
|> Conn.put_private(:plug_session, session)
|> Conn.put_private(:plug_session_fetch, :done)
|> Conn.register_before_send(before_send(sid, config))
end
end
defp before_send(sid, config) do
fn conn ->
case Map.get(conn.private, :plug_session_info) do
:write ->
value = put_session(sid, conn, config)
put_cookie(value, conn, config)
:drop ->
if sid do
delete_session(sid, conn, config)
delete_cookie(conn, config)
else
conn
end
:renew ->
if sid, do: delete_session(sid, conn, config)
value = put_session(nil, conn, config)
put_cookie(value, conn, config)
:ignore ->
conn
nil ->
conn
end
end
end
defp put_session(sid, conn, %{store: store, store_config: store_config}),
do: store.put(conn, sid, conn.private[:plug_session], store_config)
defp delete_session(sid, conn, %{store: store, store_config: store_config}),
do: store.delete(conn, sid, store_config)
defp put_cookie(value, conn, %{cookie_opts: cookie_opts, key: key}),
do: Conn.put_resp_cookie(conn, key, value, cookie_opts)
defp delete_cookie(conn, %{cookie_opts: cookie_opts, key: key}),
do: Conn.delete_resp_cookie(conn, key, cookie_opts)
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
defmodule Plug.Session.COOKIE do
@moduledoc """
Stores the session in a cookie.
This cookie store is based on `Plug.Crypto.MessageVerifier`
and `Plug.Crypto.Message.Encryptor` which encrypts and signs
each cookie to ensure they can't be read nor tampered with.
Since this store uses crypto features, it requires you to
set the `:secret_key_base` field in your connection. This
can be easily achieved with a plug:
plug :put_secret_key_base
def put_secret_key_base(conn, _) do
put_in conn.secret_key_base, "-- LONG STRING WITH AT LEAST 64 BYTES --"
end
## Options
* `:encryption_salt` - a salt used with `conn.secret_key_base` to generate
a key for encrypting/decrypting a cookie.
* `:signing_salt` - a salt used with `conn.secret_key_base` to generate a
key for signing/verifying a cookie;
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to 1000;
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to 32;
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to `:sha256';
* `:serializer` - cookie serializer module that defines `encode/1` and
`decode/1` returning an `{:ok, value}` tuple. Defaults to
`:external_term_format`.
## Examples
# Use the session plug with the table name
plug Plug.Session, store: :cookie,
key: "_my_app_session",
encryption_salt: "cookie store encryption salt",
signing_salt: "cookie store signing salt",
key_length: 64
"""
@behaviour Plug.Session.Store
alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageVerifier
alias Plug.Crypto.MessageEncryptor
def init(opts) do
encryption_salt = opts[:encryption_salt]
signing_salt = check_signing_salt(opts)
iterations = Keyword.get(opts, :key_iterations, 1000)
length = Keyword.get(opts, :key_length, 32)
digest = Keyword.get(opts, :key_digest, :sha256)
key_opts = [iterations: iterations,
length: length,
digest: digest,
cache: Plug.Keys]
serializer = check_serializer(opts[:serializer] || :external_term_format)
%{encryption_salt: encryption_salt,
signing_salt: signing_salt,
key_opts: key_opts,
serializer: serializer}
end
def get(conn, cookie, opts) do
key_opts = opts.key_opts
if key = opts.encryption_salt do
MessageEncryptor.verify_and_decrypt(cookie,
derive(conn, key, key_opts),
derive(conn, opts.signing_salt, key_opts))
else
MessageVerifier.verify(cookie, derive(conn, opts.signing_salt, key_opts))
end |> decode(opts.serializer)
end
def put(conn, _sid, term, opts) do
binary = encode(term, opts.serializer)
key_opts = opts.key_opts
if key = opts.encryption_salt do
MessageEncryptor.encrypt_and_sign(binary,
derive(conn, key, key_opts),
derive(conn, opts.signing_salt, key_opts))
else
MessageVerifier.sign(binary, derive(conn, opts.signing_salt, key_opts))
end
end
def delete(_conn, _sid, _opts) do
:ok
end
defp encode(term, :external_term_format) do
:erlang.term_to_binary(term)
end
defp encode(term, serializer) do
{:ok, binary} = serializer.encode(term)
binary
end
defp decode({:ok, binary}, :external_term_format) do
{:term,
try do
:erlang.binary_to_term(binary)
rescue
_ -> %{}
end}
end
defp decode({:ok, binary}, serializer) do
case serializer.decode(binary) do
{:ok, term} -> {:custom, term}
_ -> {:custom, %{}}
end
end
defp decode(:error, _serializer) do
{nil, %{}}
end
defp derive(conn, key, key_opts) do
conn.secret_key_base
|> validate_secret_key_base()
|> KeyGenerator.generate(key, key_opts)
end
defp validate_secret_key_base(nil), do:
raise(ArgumentError, "cookie store expects conn.secret_key_base to be set")
defp validate_secret_key_base(secret_key_base) when byte_size(secret_key_base) < 64, do:
raise(ArgumentError, "cookie store expects conn.secret_key_base to be at least 64 bytes")
defp validate_secret_key_base(secret_key_base), do:
secret_key_base
defp check_signing_salt(opts) do
case opts[:signing_salt] do
nil -> raise ArgumentError, "cookie store expects :signing_salt as option"
salt -> salt
end
end
defp check_serializer(serializer) when is_atom(serializer), do: serializer
defp check_serializer(_), do:
raise(ArgumentError, "cookie store expects :serializer option to be a module")
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
defmodule Plug.Session.ETS do
@moduledoc """
Stores the session in an in-memory ETS table.
This store does not create the ETS table; it expects that an
existing named table with public properties is passed as an
argument.
We don't recommend using this store in production as every
session will be stored in ETS and never cleaned until you
create a task responsible for cleaning up old entries.
Also, since the store is in-memory, it means sessions are
not shared between servers. If you deploy to more than one
machine, using this store is again not recommended.
This store, however, can be used as an example for creating
custom storages, based on Redis, Memcached, or a database
itself.
## Options
* `:table` - ETS table name (required)
For more information on ETS tables, visit the Erlang documentation at
http://www.erlang.org/doc/man/ets.html.
## Storage
The data is stored in ETS in the following format:
{sid :: String.t, data :: map, timestamp :: :erlang.timestamp}
The timestamp is updated whenever there is a read or write to the
table and it may be used to detect if a session is still active.
## Examples
# Create an ETS table when the application starts
:ets.new(:session, [:named_table, :public, read_concurrency: true])
# Use the session plug with the table name
plug Plug.Session, store: :ets, key: "sid", table: :session
"""
@behaviour Plug.Session.Store
@max_tries 100
def init(opts) do
Keyword.fetch!(opts, :table)
end
def get(_conn, sid, table) do
case :ets.lookup(table, sid) do
[{^sid, data, _timestamp}] ->
:ets.update_element(table, sid, {3, now()})
{sid, data}
[] ->
{nil, %{}}
end
end
def put(_conn, nil, data, table) do
put_new(data, table)
end
def put(_conn, sid, data, table) do
:ets.insert(table, {sid, data, now()})
sid
end
def delete(_conn, sid, table) do
:ets.delete(table, sid)
:ok
end
defp put_new(data, table, counter \\ 0)
when counter < @max_tries do
sid = :crypto.strong_rand_bytes(96) |> Base.encode64
if :ets.insert_new(table, {sid, data, now()}) do
sid
else
put_new(data, table, counter + 1)
end
end
defp now() do
:os.timestamp()
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
defmodule Plug.Session.Store do
@moduledoc """
Specification for session stores.
"""
use Behaviour
@type sid :: term | nil
@type cookie :: binary
@type session :: map
@doc """
Initializes the store.
The options returned from this function will be given
to `get/3`, `put/4` and `delete/3`.
"""
defcallback init(Plug.opts) :: Plug.opts
@doc """
Parses the given cookie.
Returns a session id and the session contents. The session id is any
value that can be used to identify the session by the store.
The session id may be nil in case the cookie does not identify any
value in the store. The session contents must be a map.
"""
defcallback get(Plug.Conn.t, cookie, Plug.opts) :: {sid, session}
@doc """
Stores the session associated with given session id.
If `nil` is given as id, a new session id should be
generated and returned.
"""
defcallback put(Plug.Conn.t, sid, any, Plug.opts) :: cookie
@doc """
Removes the session associated with given session id from the store.
"""
defcallback delete(Plug.Conn.t, sid, Plug.opts) :: :ok
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
defmodule Plug.SSL do
@moduledoc """
A plug to force SSL connections.
If the scheme of a request is https, it'll add a `strict-transport-security`
header to enable HTTP Strict Transport Security.
Otherwise, the request will be redirected to a corresponding location
with the `https` scheme by setting the `location` header of the response.
The status code will be 301 if the method of `conn` is `GET` or `HEAD`,
or 307 in other situations.
## x-forwaded-proto
If your Plug application is behind a proxy that handles HTTPS, you will
need to tell Plug to parse the proper protocol from the "x-forwarded-proto"
header. This can be done using the `:rewrite_on` option:
use Plug.SSL, rewrite_on: [:x_forwarded_proto]
The command above will effectively change the value of `conn.scheme` by
the one sent in "x-forwarded-proto".
Since rewriting the scheme based on "x-forwarded-proto" can open up
security vulnerabilities, only provide the option above if:
* Your app is behind a proxy
* Your proxy strips "x-forwarded-proto" headers from all incoming requests
* Your proxy sets the "x-forwarded-proto" and sends it to Plug
## Options
* `:rewrite_on` - rewrites the scheme to https based on the given headers
* `:hsts` - a boolean on enabling HSTS or not, defaults to true.
* `:expires` - seconds to expires for HSTS, defaults to 31536000 (a year).
* `:subdomains` - a boolean on including subdomains or not in HSTS,
defaults to false.
* `:host` - a new host to redirect to if the request's scheme is `http`.
## Port
It is not possible to directly configure the port in `Plug.SSL` because
HSTS expects the port to be 443 for SSL. If you are not using HSTS and
wants to redirect to HTTPS on another port, you can sneak it alongside
the host, for example: `host: "example.com:443"`.
"""
@behaviour Plug
import Plug.Conn
alias Plug.Conn
def init(opts) do
{hsts_header(opts), Keyword.get(opts, :host), Keyword.get(opts, :rewrite_on, [])}
end
def call(conn, {hsts, host, rewrites}) do
conn = rewrite_on(conn, rewrites)
if conn.scheme == :https do
put_hsts_header(conn, hsts)
else
redirect_to_https(conn, host)
end
end
defp rewrite_on(conn, rewrites) do
Enum.reduce rewrites, conn, fn
:x_forwarded_proto, acc ->
if get_req_header(acc, "x-forwarded-proto") == ["https"] do
%{acc | scheme: :https}
else
acc
end
other, _acc ->
raise "unknown rewrite: #{inspect other}"
end
end
# http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
defp hsts_header(opts) do
if Keyword.get(opts, :hsts, true) do
expires = Keyword.get(opts, :expires, 31536000)
subdomains = Keyword.get(opts, :subdomains, false)
"max-age=#{expires}" <>
if(subdomains, do: "; includeSubDomains", else: "")
end
end
defp put_hsts_header(conn, hsts_header) when is_binary(hsts_header) do
put_resp_header(conn, "strict-transport-security", hsts_header)
end
defp put_hsts_header(conn, _), do: conn
defp redirect_to_https(%Conn{host: host} = conn, custom_host) do
status = if conn.method in ~w(HEAD GET), do: 301, else: 307
location = "https://" <> (custom_host || host) <>
conn.request_path <> qs(conn.query_string)
conn
|> put_resp_header("location", location)
|> send_resp(status, "")
|> halt
end
defp qs(""), do: ""
defp qs(qs), do: "?" <> qs
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
defmodule Plug.Static do
@moduledoc """
A plug for serving static assets.
It requires two options on initialization:
* `:at` - the request path to reach for static assets.
It must be a string.
* `:from` - the filesystem path to read static assets from.
It must be a string, containing a file system path, an
atom representing the application name, where assets will
be served from the priv/static, or a tuple containing the
application name and directory to serve them besides
priv/static.
The preferred form is to use `:from` with an atom or tuple,
since it will make your application independent from the
starting directory.
If a static asset cannot be found, `Plug.Static` simply forwards
the connection to the rest of the pipeline.
## Cache mechanisms
`Plug.Static` uses etags for HTTP caching. This means browsers/clients
should cache assets on the first request and validate the cache on
following requests, not downloading the static asset once again if it
has not changed. The cache-control for etags is specified by the
`cache_control_for_etags` option and defaults to "public".
However, `Plug.Static` also supports direct cache control by using
versioned query strings. If the request query string starts with
"?vsn=", `Plug.Static` assumes the application is versioning assets
and does not set the `ETag` header, meaning the cache behaviour will
be specified solely by the `cache_control_for_vsn_requests` config,
which defaults to "public, max-age=31536000".
## Options
* `:gzip` - given a request for `FILE`, serves `FILE.gz` if it exists
in the static directory and if the `accept-encoding` header is set
to allow gzipped content (defaults to `false`).
* `:cache_control_for_etags` - sets the cache header for requests
that use etags. Defaults to `"public"`.
* `:cache_control_for_vsn_requests` - sets the cache header for
requests starting with "?vsn=" in the query string. Defaults to
`"public, max-age=31536000"`.
* `:only` - filters which paths to look up. This is useful to avoid
file system traversals on every request when this plug is mounted
at `"/"`. Defaults to `nil` (no filtering).
* `:headers` - other headers to be set when serving static assets.
## Examples
This plug can be mounted in a `Plug.Builder` pipeline as follows:
defmodule MyPlug do
use Plug.Builder
plug Plug.Static, at: "/public", from: :my_app
plug :not_found
def not_found(conn, _) do
send_resp(conn, 404, "not found")
end
end
"""
@behaviour Plug
@allowed_methods ~w(GET HEAD)
import Plug.Conn
alias Plug.Conn
# In this module, the `:prim_info` Erlang module along with the `:file_info`
# record are used instead of the more common and Elixir-y `File` module and
# `File.Stat` struct, respectively. The reason behind this is performance: all
# the `File` operations pass through a single process in order to support node
# operations that we simply don't need when serving assets.
require Record
Record.defrecordp :file_info, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")
defmodule InvalidPathError do
defexception message: "invalid path for static asset", plug_status: 400
end
def init(opts) do
at = Keyword.fetch!(opts, :at)
from = Keyword.fetch!(opts, :from)
gzip = Keyword.get(opts, :gzip, false)
only = Keyword.get(opts, :only, nil)
qs_cache = Keyword.get(opts, :cache_control_for_vsn_requests, "public, max-age=31536000")
et_cache = Keyword.get(opts, :cache_control_for_etags, "public")
headers = Keyword.get(opts, :headers, %{})
from =
case from do
{_, _} -> from
_ when is_atom(from) -> {from, "priv/static"}
_ when is_binary(from) -> from
_ -> raise ArgumentError, ":from must be an atom, a binary or a tuple"
end
{Plug.Router.Utils.split(at), from, gzip, qs_cache, et_cache, only, headers}
end
def call(conn = %Conn{method: meth}, {at, from, gzip, qs_cache, et_cache, only, headers})
when meth in @allowed_methods do
# subset/2 returns the segments in `conn.path_info` without the
# segments at the beginning that are shared with `at`.
segments = subset(at, conn.path_info) |> Enum.map(&URI.decode/1)
cond do
not allowed?(only, segments) ->
conn
invalid_path?(segments) ->
raise InvalidPathError
true ->
path = path(from, segments)
serve_static(file_encoding(conn, path, gzip), segments, gzip, qs_cache, et_cache, headers)
end
end
def call(conn, _opts) do
conn
end
defp allowed?(_only, []), do: false
defp allowed?(nil, _list), do: true
defp allowed?(only, [h|_]), do: h in only
defp serve_static({:ok, conn, file_info, path}, segments, gzip, qs_cache, et_cache, headers) do
case put_cache_header(conn, qs_cache, et_cache, file_info) do
{:stale, conn} ->
content_type = segments |> List.last |> Plug.MIME.path
conn
|> maybe_add_vary(gzip)
|> put_resp_header("content-type", content_type)
|> merge_resp_headers(headers)
|> send_file(200, path)
|> halt
{:fresh, conn} ->
conn
|> send_resp(304, "")
|> halt
end
end
defp serve_static({:error, conn}, _segments, _gzip, _qs_cache, _et_cache, _headers) do
conn
end
defp maybe_add_vary(conn, true) do
# If we serve gzip at any moment, we need to set the proper vary
# header regardless of whether we are serving gzip content right now.
# See: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
update_in conn.resp_headers, &[{"vary", "Accept-Encoding"}|&1]
end
defp maybe_add_vary(conn, false) do
conn
end
defp put_cache_header(%Conn{query_string: "vsn=" <> _} = conn, qs_cache, _et_cache, _file_info)
when is_binary(qs_cache) do
{:stale, put_resp_header(conn, "cache-control", qs_cache)}
end
defp put_cache_header(conn, _qs_cache, et_cache, file_info) when is_binary(et_cache) do
etag = etag_for_path(file_info)
conn =
conn
|> put_resp_header("cache-control", et_cache)
|> put_resp_header("etag", etag)
if etag in get_req_header(conn, "if-none-match") do
{:fresh, conn}
else
{:stale, conn}
end
end
defp put_cache_header(conn, _, _, _) do
{:stale, conn}
end
defp etag_for_path(file_info) do
file_info(size: size, mtime: mtime) = file_info
{size, mtime} |> :erlang.phash2() |> Integer.to_string(16)
end
defp file_encoding(conn, path, gzip) do
path_gz = path <> ".gz"
cond do
gzip && gzip?(conn) && (file_info = regular_file_info(path_gz)) ->
{:ok, put_resp_header(conn, "content-encoding", "gzip"), file_info, path_gz}
file_info = regular_file_info(path) ->
{:ok, conn, file_info, path}
true ->
{:error, conn}
end
end
defp regular_file_info(path) do
case :prim_file.read_file_info(path) do
{:ok, file_info(type: :regular) = file_info} ->
file_info
_ ->
nil
end
end
defp gzip?(conn) do
gzip_header? = &String.contains?(&1, ["gzip", "*"])
Enum.any? get_req_header(conn, "accept-encoding"), fn accept ->
accept |> Plug.Conn.Utils.list() |> Enum.any?(gzip_header?)
end
end
defp path({app, from}, segments) when is_atom(app) and is_binary(from),
do: Path.join([Application.app_dir(app), from|segments])
defp path(from, segments),
do: Path.join([from|segments])
defp subset([h|expected], [h|actual]),
do: subset(expected, actual)
defp subset([], actual),
do: actual
defp subset(_, _),
do: []
defp invalid_path?([h|_]) when h in [".", "..", ""], do: true
defp invalid_path?([h|t]), do: String.contains?(h, ["/", "\\", ":"]) or invalid_path?(t)
defp invalid_path?([]), do: false
end
|
> > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
defmodule Plug.Supervisor do
@moduledoc false
def start_link() do
Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
end
def init(:ok) do
import Supervisor.Spec
children = [
worker(Plug.Upload, [])
]
Plug.Keys = :ets.new(Plug.Keys, [:named_table, :public, read_concurrency: true])
supervise(children, strategy: :one_for_one)
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 |
<!DOCTYPE html>
<html>
<head>
<title><%= @title %> at <%= method(@conn) %> <%= @conn.request_path %></title>
<style>
/* Basic reset */
* {
margin: 0;
padding: 0;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
vertical-align: top;
text-align: left;
}
textarea {
resize: none;
}
body {
font-size: 10pt;
}
body, td, input, textarea {
font-family: helvetica neue, lucida grande, sans-serif;
line-height: 1.5;
color: #333;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}
html {
background: #f0f0f5;
}
/* ---------------------------------------------------------------------
* Basic layout
* --------------------------------------------------------------------- */
/* Small */
@media screen and (max-width: 1100px) {
html {
overflow-y: scroll;
}
body {
margin: 0 20px;
}
header.exception {
margin: 0 -20px;
}
nav.sidebar {
padding: 0;
margin: 20px 0;
}
ul.frames {
max-height: 200px;
overflow: auto;
}
}
/* Wide */
@media screen and (min-width: 1100px) {
header.exception {
position: fixed;
top: 0;
left: 0;
right: 0;
}
nav.sidebar,
.conn_info {
position: fixed;
top: 95px;
bottom: 0;
box-sizing: border-box;
overflow-y: auto;
overflow-x: hidden;
}
nav.sidebar {
width: 40%;
left: 20px;
top: 115px;
bottom: 20px;
}
.conn_info {
right: 0;
left: 40%;
padding: 20px;
padding-left: 10px;
margin-left: 30px;
}
}
nav.sidebar {
background: #d3d3da;
border-top: solid 3px #4E2A8E;
border-bottom: solid 3px #4E2A8E;
border-radius: 4px;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
}
/* ---------------------------------------------------------------------
* Header
* --------------------------------------------------------------------- */
header.exception {
padding: 18px 20px;
height: 59px;
min-height: 59px;
overflow: hidden;
background-color: #20202a;
color: #aaa;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);
font-weight: 200;
box-shadow: inset 0 -5px 3px -3px rgba(0, 0, 0, 0.05), inset 0 -1px 0 rgba(0, 0, 0, 0.05);
-webkit-text-smoothing: antialiased;
}
/* Heading */
header.exception h2 {
font-weight: 200;
font-size: 11pt;
padding-bottom: 2pt;
}
header.exception h2,
header.exception p {
line-height: 1.4em;
height: 1.4em;
overflow: hidden;
white-space: pre;
text-overflow: ellipsis;
}
header.exception h2 strong {
font-weight: 700;
color: #7E5ABE;
}
header.exception p {
font-weight: 200;
font-size: 18pt;
color: white;
}
header.exception:hover {
height: auto;
overflow-y: auto;
max-height: 60%;
z-index: 2;
}
header.exception:hover h2,
header.exception:hover p {
padding-right: 20px;
word-wrap: break-word;
height: auto;
}
@media screen and (max-width: 1100px) {
header.exception {
height: auto;
}
header.exception h2,
header.exception p {
padding-right: 20px;
overflow-y: auto;
word-wrap: break-word;
height: auto;
max-height: 7em;
}
}
/* ---------------------------------------------------------------------
* Navigation
* --------------------------------------------------------------------- */
nav.tabs {
border-bottom: solid 1px #ddd;
background-color: #eee;
text-align: center;
padding: 6px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
nav.tabs a {
display: inline-block;
height: 22px;
line-height: 22px;
padding: 0 10px;
text-decoration: none;
font-size: 8pt;
font-weight: bold;
color: #999;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
}
nav.tabs a.selected {
color: white;
background: rgba(0, 0, 0, 0.5);
border-radius: 16px;
box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.1);
text-shadow: 0 0 4px rgba(0, 0, 0, 0.4), 0 1px 0 rgba(0, 0, 0, 0.4);
}
/* ---------------------------------------------------------------------
* Sidebar
* --------------------------------------------------------------------- */
ul.frames {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* Each item */
ul.frames li {
background-color: #f8f8f8;
background: -webkit-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
background: -moz-linear-gradient(top, #f8f8f8 80%, #f0f0f0);
background: linear-gradient(top, #f8f8f8 80%, #f0f0f0);
box-shadow: inset 0 -1px 0 #e2e2e2;
padding: 7px 20px;
cursor: pointer;
overflow: hidden;
}
ul.frames .name,
ul.frames .location {
overflow: hidden;
height: 1.5em;
white-space: nowrap;
word-wrap: none;
text-overflow: ellipsis;
}
ul.frames .app {
color: #4E2A8E;
}
ul.frames .location {
font-size: 0.85em;
font-weight: 400;
color: #999;
}
ul.frames .line {
font-weight: bold;
}
/* Selected frame */
ul.frames li.selected {
background: #88A;
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1), inset 0 2px 0 rgba(255, 255, 255, 0.01), inset 0 -1px 0 rgba(0, 0, 0, 0.1);
}
ul.frames li.selected .name,
ul.frames li.selected .function,
ul.frames li.selected .location {
color: white;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}
ul.frames li.selected .location {
opacity: 0.6;
}
/* Iconography */
ul.frames li {
padding-left: 60px;
position: relative;
}
ul.frames li .icon {
display: block;
width: 20px;
height: 20px;
line-height: 20px;
border-radius: 15px;
text-align: center;
background: white;
border: solid 2px #ccc;
font-size: 9pt;
font-weight: 200;
font-style: normal;
position: absolute;
top: 14px;
left: 20px;
}
ul.frames .icon.app {
background: #808090;
border-color: #555;
}
ul.frames .icon.app:before {
content: 'A';
color: white;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
}
/* Responsiveness -- flow to single-line mode */
@media screen and (max-width: 1100px) {
ul.frames li {
padding-top: 6px;
padding-bottom: 6px;
padding-left: 36px;
line-height: 1.3;
}
ul.frames li .icon {
width: 11px;
height: 11px;
line-height: 11px;
top: 7px;
left: 10px;
font-size: 5pt;
}
ul.frames .name,
ul.frames .location {
display: inline-block;
line-height: 1.3;
height: 1.3em;
}
ul.frames .name {
margin-right: 10px;
}
}
/* ---------------------------------------------------------------------
* Monospace
* --------------------------------------------------------------------- */
pre, code, textarea {
font-family: menlo, lucida console, monospace;
font-size: 8pt;
}
/* ---------------------------------------------------------------------
* Display area
* --------------------------------------------------------------------- */
.trace_info {
background: #fff;
padding: 6px;
border-radius: 3px;
margin-bottom: 2px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.03), 1px 1px 0 rgba(0, 0, 0, 0.05), -1px 1px 0 rgba(0, 0, 0, 0.05), 0 0 0 4px rgba(0, 0, 0, 0.04);
}
/* Titlebar */
.trace_info .title {
background: #f1f1f1;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3);
overflow: hidden;
padding: 6px 10px;
border: solid 1px #ccc;
border-bottom: 0;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.trace_info .title .name,
.trace_info .title .location {
font-size: 9pt;
line-height: 26px;
height: 26px;
overflow: hidden;
}
.trace_info .title .location {
float: left;
font-weight: bold;
font-size: 10pt;
}
.trace_info .title .location a {
color:inherit;
text-decoration:none;
border-bottom:1px solid #aaaaaa;
}
.trace_info .title .location a:hover {
border-color:#666666;
}
.trace_info .title .name {
float: right;
font-weight: 200;
}
.code, .unavailable {
background: #fff;
padding: 5px;
box-shadow: inset 3px 3px 3px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
}
.code {
margin-bottom: -1px;
padding: 10px 0;
overflow: auto;
}
.code .ln {
width: 35px;
margin-right: 15px;
text-align: right;
display: inline-block;
}
.code .no_snippet {
padding: 5px 15px 2px;
font-size: 9pt;
}
/* Source unavailable */
p.unavailable {
padding: 20px 0 40px 0;
text-align: center;
color: #b99;
font-weight: bold;
}
p.unavailable:before {
content: '\00d7';
display: block;
color: #daa;
text-align: center;
font-size: 40pt;
font-weight: normal;
margin-bottom: -10px;
}
@-webkit-keyframes highlight {
0% { background: rgba(220, 30, 30, 0.3); }
100% { background: rgba(220, 30, 30, 0.1); }
}
@-moz-keyframes highlight {
0% { background: rgba(220, 30, 30, 0.3); }
100% { background: rgba(220, 30, 30, 0.1); }
}
@keyframes highlight {
0% { background: rgba(220, 30, 30, 0.3); }
100% { background: rgba(220, 30, 30, 0.1); }
}
.code .highlight {
background: rgba(220, 30, 30, 0.1);
-webkit-animation: highlight 400ms linear 1;
-moz-animation: highlight 400ms linear 1;
animation: highlight 400ms linear 1;
}
/* ---------------------------------------------------------------------
* Variable infos
* --------------------------------------------------------------------- */
.sub {
padding: 10px 0;
margin: 10px 0;
}
.sub:before {
content: '';
display: block;
width: 100%;
height: 4px;
border-radius: 2px;
background: rgba(0, 150, 200, 0.05);
box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.7), inset 0 0 0 1px rgba(0, 0, 0, 0.04), inset 2px 2px 2px rgba(0, 0, 0, 0.07);
}
.sub h3 {
color: #4E2A8E;
font-size: 1.1em;
margin: 10px 0;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6);
-webkit-font-smoothing: antialiased;
}
.sub .inset {
overflow-y: auto;
}
.sub table {
table-layout: fixed;
}
.sub table td {
border-top: dotted 1px #ddd;
padding: 7px 1px;
}
.sub table td.name {
width: 150px;
font-weight: bold;
font-size: 0.8em;
padding-right: 20px;
word-wrap: break-word;
}
.sub table td pre {
max-height: 15em;
overflow-y: auto;
}
.sub table td pre {
width: 100%;
word-wrap: break-word;
white-space: normal;
}
/* "(object doesn't support inspect)" */
.sub .unsupported {
font-family: sans-serif;
color: #777;
}
/* ---------------------------------------------------------------------
* Scrollbar
* --------------------------------------------------------------------- */
nav.sidebar::-webkit-scrollbar,
.inset pre::-webkit-scrollbar,
.code::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.inset pre::-webkit-scrollbar-thumb,
.code::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 5px;
}
nav.sidebar::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.0);
border-radius: 5px;
}
nav.sidebar:hover::-webkit-scrollbar-thumb {
background-color: #999;
background: -webkit-linear-gradient(left, #aaa, #999);
}
.code:hover::-webkit-scrollbar-thumb {
background: #888;
}
</style>
</head>
<body>
<div class="top">
<header class="exception">
<h2><strong><%= h(@title) %></strong> <span>at <%= method(@conn) %> <%= @conn.request_path %></span></h2>
<p><%= h(@message) %></p>
</header>
</div>
<section class="backtrace">
<nav class="sidebar">
<nav class="tabs">
<a href="#" id="app_frames">App frames</a>
<a href="#" id="all_frames">All frames</a>
</nav>
<ul class="frames">
<%= for frame <- @frames do %>
<li class="<%= frame.context %>" style="display: none" data-context="<%= frame.context %>" data-index="<%= frame.index %>">
<span class='stroke'></span>
<i class="icon <%= frame.context %>"></i>
<div class="info">
<div class="name">
<strong><%= h(frame.info) %></strong>
<%= if app = frame.app do %>
<span class="app">(<%= app %>)</span>
<% end %>
</div>
<div class="location">
<span class="filename"><%= frame.file %></span>
<%= if frame.line do %>
(line <span class="line"><%= frame.line %></span>)
<% end %>
</div>
</div>
</li>
<% end %>
</ul>
</nav>
<div class="conn_info">
<%= for frame <- @frames do %>
<div class="frame_info" id="frame_info_<%= frame.index %>" style="display: none;">
<header class="trace_info">
<div class="title">
<h2 class="name">
<%= frame.info %>
<%= if app = frame.app do %>
<span class="app">(<%= app %>)</span>
<% end %>
</h2>
<div class="location"><span class="filename"><a href="<%= frame.link %>"><%= frame.file %></a></span></div>
</div>
<div class="code">
<%= if snippet = frame.snippet do %>
<%= for {index, line, highlight} <- snippet do %>
<pre class="<%= if highlight, do: "highlight" %>"><span class="ln"><%= index %></span><span><%=h line %></span></pre>
<% end %>
<% else %>
<p class="no_snippet">No code snippets available.</p>
<% end %>
</div>
</header>
<div class="sub">
<h3>Frame</h3>
<div class="inset variables">
<table class="var_table">
<tr>
<td class="name">Function</td>
<td><%= h(frame.info) %> (<%= h(frame.app) %>)</td>
</tr>
<%= if frame.args != [] do %>
<tr>
<td class="name">Args</td>
<td><%= h(inspect frame.args) %></td>
</tr>
<% end %>
</table>
</div>
</div>
</div>
<% end %>
<div class="sub">
<h3>Request info</h3>
<div class="inset variables">
<table class="var_table">
<tr>
<td class="name">URI</td>
<td><%= url(@conn) %></td>
</tr>
<tr>
<td class="name">Query String</td>
<td><%= @conn.query_string %></td>
</tr>
<tr>
<td class="name">Peer</td>
<td><%= peer(@conn) %></td>
</tr>
</table>
</div>
</div>
<div class="sub">
<h3>Headers</h3>
<div class="inset variables">
<table class="var_table">
<%= for {key, value} <- Enum.sort(@conn.req_headers) do %>
<tr>
<td class="name"><%= key %></td>
<td><%= value %></td>
</tr>
<% end %>
</table>
</div>
</div>
<%= if @params do %>
<div class="sub">
<h3>Params</h3>
<div class="inset variables">
<table class="var_table">
<%= for {key, value} <- @params do %>
<tr>
<td class="name"><%= key %></td>
<td><pre><%= inspect value %></pre></td>
</tr>
<% end %>
</table>
</div>
</div>
<% end %>
<%= if @session do %>
<div class="sub">
<h3>Session</h3>
<div class="inset variables">
<table class="var_table">
<%= for {key, value} <- @session do %>
<tr>
<td class="name"><%= key %></td>
<td><pre><%= inspect value %></pre></td>
</tr>
<% end %>
</table>
</div>
</div>
<% end %>
</div>
</section>
</body>
<script>
var previousFrame = null;
var previousFrameInfo = null;
var allFrames = document.querySelectorAll("ul.frames li");
var allFrameInfos = document.querySelectorAll(".frame_info");
for(var i = 0; i < allFrames.length; i++) {
(function(i, el) {
var el = allFrames[i];
el.onclick = function() {
if(previousFrame) previousFrame.className = "";
el.className = "selected";
previousFrame = el;
selectFrameInfo(el.attributes["data-index"].value);
};
})(i);
};
function selectFrameInfo(index) {
var el = allFrameInfos[index];
if(previousFrameInfo) previousFrameInfo.style.display = "none";
previousFrameInfo = el;
el.style.display = "block";
};
(
document.querySelector(".frames li.app") ||
document.querySelector(".frames li")
).onclick();
var appFramesButton = document.getElementById("app_frames");
var allFramesButton = document.getElementById("all_frames");
appFramesButton.onclick = function() {
allFramesButton.className = "";
appFramesButton.className = "selected";
for(var i = 0; i < allFrames.length; i++) {
if(allFrames[i].attributes["data-context"].value == "app") {
allFrames[i].style.display = "block";
allFrames[i].setAttribute("data-display", "block");
} else {
allFrames[i].style.display = "none";
allFrames[i].setAttribute("data-display", "none");
};
};
return false;
};
allFramesButton.onclick = function() {
appFramesButton.className = "";
allFramesButton.className = "selected";
for(var i = 0; i < allFrames.length; i++) {
allFrames[i].style.display = "block";
allFrames[i].setAttribute("data-display", "block");
};
return false;
};
appFramesButton.onclick();
if(document.querySelectorAll("ul.frames li[data-display=\"block\"]").length == 0) {
allFramesButton.onclick();
}
</script>
</html>
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
defmodule Plug.Test do
@moduledoc """
Conveniences for testing plugs.
This module can be used in your test cases, like this:
use ExUnit.Case, async: true
use Plug.Test
Using this module will:
* import all the functions from this module
* import all the functions from the `Plug.Conn` module
"""
@doc false
defmacro __using__(_) do
quote do
import Plug.Test
import Plug.Conn
end
end
alias Plug.Conn
@typep params :: binary | list | map | nil
@doc """
Creates a test connection.
The request `method` and `path` are required arguments. `method` may be any
value that implements `to_string/1` and it will properly converted and
normalized (e.g., `:get` or `"post"`).
The `params_or_body` field must be one of:
* `nil` - meaning there is no body;
* a binary - containing a request body. For such cases, `:headers`
must be given as option with a content-type;
* a map or list - containing the parameters which will automatically
set the content-type to multipart. The map or list may contain
other lists or maps and all entries will be normalized to string
keys;
## Examples
conn(:get, "/foo", "bar=10")
conn(:post, "/")
conn("patch", "/", "") |> put_req_header("content-type", "application/json")
"""
@spec conn(String.Chars.t, binary, params) :: Conn.t
def conn(method, path, params_or_body \\ nil) do
Plug.Adapters.Test.Conn.conn(%Plug.Conn{}, method, path, params_or_body)
end
@doc """
Returns the sent response.
This function is useful when the code being invoked crashes and
there is a need to verify a particular response was sent even with
the crash. It returns a tuple with `{status, headers, body}`.
"""
def sent_resp(%Conn{adapter: {Plug.Adapters.Test.Conn, %{ref: ref}}}) do
case receive_resp(ref) do
:no_resp ->
raise "no sent response available for the given connection. " <>
"Maybe the application did not send anything?"
response ->
case receive_resp(ref) do
:no_resp ->
send(self, {ref, response})
response
_otherwise ->
raise "a response for the given connection has been sent more than once"
end
end
end
defp receive_resp(ref) do
receive do
{^ref, response} -> response
after
0 -> :no_resp
end
end
@doc """
Puts a request cookie.
"""
@spec put_req_cookie(Conn.t, binary, binary) :: Conn.t
def put_req_cookie(conn, key, value) when is_binary(key) and is_binary(value) do
conn = delete_req_cookie(conn, key)
%{conn | req_headers: [{"cookie", "#{key}=#{value}"}|conn.req_headers]}
end
@doc """
Deletes a request cookie.
"""
@spec delete_req_cookie(Conn.t, binary) :: Conn.t
def delete_req_cookie(%Conn{req_cookies: %Plug.Conn.Unfetched{}} = conn, key)
when is_binary(key) do
key = "#{key}="
size = byte_size(key)
fun = &match?({"cookie", value} when binary_part(value, 0, size) == key, &1)
%{conn | req_headers: Enum.reject(conn.req_headers, fun)}
end
def delete_req_cookie(_conn, key) when is_binary(key) do
raise ArgumentError,
message: "cannot put/delete request cookies after cookies were fetched"
end
@doc """
Moves cookies from a connection into a new connection for subsequent requests.
This function copies the cookie information in `old_conn` into `new_conn`,
emulating multiple requests done by clients where cookies are always passed
forward, and returns the new version of `new_conn`.
"""
@spec recycle_cookies(Conn.t, Conn.t) :: Conn.t
def recycle_cookies(new_conn, old_conn) do
Enum.reduce Plug.Conn.fetch_cookies(old_conn).cookies, new_conn, fn
{key, value}, acc -> put_req_cookie(acc, to_string(key), value)
end
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
defmodule Plug.Upload do
@moduledoc """
A server (a `GenServer` specifically) that manages uploaded files.
Uploaded files are stored in a temporary directory
and removed from that directory after the process that
requested the file dies.
During the request, files are represented with
a `Plug.Upload` struct that contains three fields:
* `:path` - the path to the uploaded file on the filesystem
* `:content_type` - the content type of the uploaded file
* `:filename` - the filename of the uploaded file given in the request
**Note**: as mentioned in the documentation for `Plug.Parsers`, the `:plug`
application has to be started in order to upload files and use the
`Plug.Upload` module.
"""
defstruct [:path, :content_type, :filename]
@type t :: %__MODULE__{
path: Path.t,
filename: binary,
content_type: binary | nil
}
@doc """
Requests a random file to be created in the upload directory
with the given prefix.
"""
@spec random_file(binary) ::
{:ok, binary} |
{:too_many_attempts, binary, pos_integer} |
{:no_tmp, [binary]}
def random_file(prefix) do
GenServer.call(plug_server, {:random, prefix})
end
@doc """
Requests a random file to be created in the upload directory
with the given prefix. Raises on failure.
"""
@spec random_file!(binary) :: binary | no_return
def random_file!(prefix) do
case random_file(prefix) do
{:ok, path} ->
path
{:too_many_attempts, tmp, attempts} ->
raise "tried #{attempts} times to create an uploaded file at #{tmp} but failed. What gives?"
{:no_tmp, _tmps} ->
raise "could not create a tmp directory to store uploads. Set PLUG_TMPDIR to a directory with write permission"
end
end
defp plug_server do
Process.whereis(__MODULE__) ||
raise "could not find process Plug.Upload. Have you started the :plug application?"
end
use GenServer
@doc """
Starts the upload handling server.
"""
def start_link() do
GenServer.start_link(__MODULE__, :ok, [name: __MODULE__])
end
## Callbacks
@temp_env_vars ~w(PLUG_TMPDIR TMPDIR TMP TEMP)s
@max_attempts 10
@doc false
def init(:ok) do
tmp = Enum.find_value @temp_env_vars, "/tmp", &System.get_env/1
cwd = Path.join(File.cwd!, "tmp")
ets = :ets.new(:plug_uploads, [:private])
{:ok, {[tmp, cwd], ets}}
end
@doc false
def handle_call({:random, prefix}, {pid, _ref}, {tmps, ets} = state) do
case find_tmp_dir(pid, tmps, ets) do
{:ok, tmp, paths} ->
{:reply, open_random_file(prefix, tmp, 0, pid, ets, paths), state}
{:no_tmp, _} = error ->
{:reply, error, state}
end
end
def handle_call(msg, from, state) do
super(msg, from, state)
end
@doc false
def handle_info({:DOWN, _ref, :process, pid, _reason}, {_, ets} = state) do
case :ets.lookup(ets, pid) do
[{pid, _tmp, paths}] ->
:ets.delete(ets, pid)
Enum.each paths, &:file.delete/1
[] ->
:ok
end
{:noreply, state}
end
def handle_info(msg, state) do
super(msg, state)
end
## Helpers
defp find_tmp_dir(pid, tmps, ets) do
case :ets.lookup(ets, pid) do
[{^pid, tmp, paths}] ->
{:ok, tmp, paths}
[] ->
if tmp = ensure_tmp_dir(tmps) do
:erlang.monitor(:process, pid)
:ets.insert(ets, {pid, tmp, []})
{:ok, tmp, []}
else
{:no_tmp, tmps}
end
end
end
defp ensure_tmp_dir(tmps) do
{mega, _, _} = :os.timestamp
subdir = "/plug-" <> i(mega)
Enum.find_value(tmps, &write_tmp_dir(&1 <> subdir))
end
defp write_tmp_dir(path) do
case File.mkdir_p(path) do
:ok -> path
{:error, _} -> nil
end
end
defp open_random_file(prefix, tmp, attempts, pid, ets, paths) when attempts < @max_attempts do
path = path(prefix, tmp)
case :file.write_file(path, "", [:write, :raw, :exclusive, :binary]) do
:ok ->
:ets.update_element(ets, pid, {3, [path|paths]})
{:ok, path}
{:error, reason} when reason in [:eexist, :eaccess] ->
open_random_file(prefix, tmp, attempts + 1, pid, ets, paths)
end
end
defp open_random_file(_prefix, tmp, attempts, _pid, _ets, _paths) do
{:too_many_attempts, tmp, attempts}
end
@compile {:inline, i: 1}
defp i(integer), do: Integer.to_string(integer)
defp path(prefix, tmp) do
{_mega, sec, micro} = :os.timestamp
scheduler_id = :erlang.system_info(:scheduler_id)
tmp <> "/" <> prefix <> "-" <> i(sec) <> "-" <> i(micro) <> "-" <> i(scheduler_id)
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
defmodule Plug.Mixfile do
use Mix.Project
@version "1.0.3"
def project do
[app: :plug,
version: @version,
elixir: "~> 1.0",
deps: deps,
package: package,
description: "A specification and conveniences for composable " <>
"modules between web applications",
name: "Plug",
docs: [extras: ["README.md"], main: "extra-readme",
source_ref: "v#{@version}",
source_url: "https://github.com/elixir-lang/plug"]]
end
# Configuration for the OTP application
def application do
[applications: [:crypto, :logger],
mod: {Plug, []}]
end
def deps do
[{:cowboy, "~> 1.0", optional: true},
{:earmark, "~> 0.1", only: :docs},
{:ex_doc, "~> 0.10", only: :docs},
{:inch_ex, only: :docs},
{:hackney, "~> 1.2.0", only: :test}]
end
defp package do
%{licenses: ["Apache 2"],
links: %{"GitHub" => "https://github.com/elixir-lang/plug"}}
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | Ranch is available thanks to the work of: Loïc Hoguin James Fish Andrew Majorov Ransom Richardson Fred Hebert Geoff Cant Klaus Trainer josh rotenberg 0x00F6 Alexander Zhuravlev Ali Sabil Andre Graf Andrew Thompson Jihyun Yu Slava Yurin Stéphane Wirtel Xiao Jia The Ranch code was initially part of Cowboy. Before it was split into a separate project, the following people worked on the code that then became Ranch: Loïc Hoguin Ali Sabil Andrew Thompson DeadZen Hunter Morris Jesper Louis Andersen Paul Oliver Roberto Ostinelli Steven Gravell |
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | # See LICENSE for licensing information. PROJECT = ranch # Options. COMPILE_FIRST = ranch_transport CT_OPTS += -pa test -ct_hooks ranch_ct_hook [] PLT_APPS = crypto public_key ssl CI_OTP = \ OTP_R15B01 OTP_R15B02 OTP_R15B03-1 \ OTP_R16B OTP_R16B01 OTP_R16B02 OTP_R16B03-1 \ OTP-17.0.2 OTP-17.1.2 OTP-17.2.2 OTP-17.3.4 OTP-17.4.1 OTP-17.5.6.2 \ OTP-18.0.2 # Dependencies. TEST_DEPS = ct_helper dep_ct_helper = git https://github.com/ninenines/ct_helper master # Standard targets. include erlang.mk # Also dialyze the tests. DIALYZER_OPTS += --src -r test # Use erl_make_certs from the tested release. ci-setup:: $(DEPS_DIR)/ct_helper cp ~/.kerl/builds/$(CI_OTP_RELEASE)/otp_src_git/lib/ssl/test/erl_make_certs.erl deps/ct_helper/src/ |
> > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 |
{application,ranch,
[{description,"Socket acceptor pool for TCP protocols."},
{vsn,"1.2.0"},
{id,"git"},
{modules,[ranch,ranch_acceptor,ranch_acceptors_sup,ranch_app,
ranch_conns_sup,ranch_listener_sup,ranch_protocol,
ranch_server,ranch_ssl,ranch_sup,ranch_tcp,
ranch_transport]},
{registered,[ranch_sup,ranch_server]},
{applications,[kernel,stdlib]},
{mod,{ranch_app,[]}},
{env,[]}]}.
|
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 |
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.PHONY: all app deps search rel docs install-docs check tests clean distclean help erlang-mk
ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST)))
ERLANG_MK_VERSION = 1.2.0-633-g8a32e48
# Core configuration.
PROJECT ?= $(notdir $(CURDIR))
PROJECT := $(strip $(PROJECT))
PROJECT_VERSION ?= rolling
# Verbosity.
V ?= 0
verbose_0 = @
verbose = $(verbose_$(V))
gen_verbose_0 = @echo " GEN " $@;
gen_verbose = $(gen_verbose_$(V))
# Temporary files directory.
ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk
export ERLANG_MK_TMP
# "erl" command.
ERL = erl +A0 -noinput -boot start_clean
# Platform detection.
# @todo Add Windows/Cygwin detection eventually.
ifeq ($(PLATFORM),)
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
PLATFORM = linux
else ifeq ($(UNAME_S),Darwin)
PLATFORM = darwin
else ifeq ($(UNAME_S),SunOS)
PLATFORM = solaris
else ifeq ($(UNAME_S),GNU)
PLATFORM = gnu
else ifeq ($(UNAME_S),FreeBSD)
PLATFORM = freebsd
else ifeq ($(UNAME_S),NetBSD)
PLATFORM = netbsd
else ifeq ($(UNAME_S),OpenBSD)
PLATFORM = openbsd
else
$(error Unable to detect platform. Please open a ticket with the output of uname -a.)
endif
export PLATFORM
endif
# Core targets.
ifneq ($(words $(MAKECMDGOALS)),1)
.NOTPARALLEL:
endif
all:: deps
$(verbose) $(MAKE) --no-print-directory app
$(verbose) $(MAKE) --no-print-directory rel
# Noop to avoid a Make warning when there's nothing to do.
rel::
$(verbose) echo -n
check:: clean app tests
clean:: clean-crashdump
clean-crashdump:
ifneq ($(wildcard erl_crash.dump),)
$(gen_verbose) rm -f erl_crash.dump
endif
distclean:: clean
help::
$(verbose) printf "%s\n" \
"erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \
"Copyright (c) 2013-2015 Loïc Hoguin <essen@ninenines.eu>" \
"" \
"Usage: [V=1] $(MAKE) [-jNUM] [target]..." \
"" \
"Core targets:" \
" all Run deps, app and rel targets in that order" \
" app Compile the project" \
" deps Fetch dependencies (if needed) and compile them" \
" search q=... Search for a package in the built-in index" \
" rel Build a release for this project, if applicable" \
" docs Build the documentation for this project" \
" install-docs Install the man pages for this project" \
" check Compile and run all tests and analysis for this project" \
" tests Run the tests for this project" \
" clean Delete temporary and output files from most targets" \
" distclean Delete all temporary and output files" \
" help Display this help and exit" \
" erlang-mk Update erlang.mk to the latest version"
# Core functions.
empty :=
space := $(empty) $(empty)
tab := $(empty) $(empty)
comma := ,
define newline
endef
define comma_list
$(subst $(space),$(comma),$(strip $(1)))
endef
# Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy.
define erlang
$(ERL) -pz $(ERLANG_MK_TMP)/rebar/ebin -eval "$(subst $(newline),,$(subst ",\",$(1)))" -- erlang.mk
endef
ifeq ($(shell which wget 2>/dev/null | wc -l), 1)
define core_http_get
wget --no-check-certificate -O $(1) $(2)|| rm $(1)
endef
else
define core_http_get.erl
ssl:start(),
inets:start(),
case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of
{ok, {{_, 200, _}, _, Body}} ->
case file:write_file("$(1)", Body) of
ok -> ok;
{error, R1} -> halt(R1)
end;
{error, R2} ->
halt(R2)
end,
halt(0).
endef
define core_http_get
$(call erlang,$(call core_http_get.erl,$(1),$(2)))
endef
endif
core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1)))
core_find = $(foreach d,$(call core_ls,$1*),$(call core_find,$d/,$2) $(filter $(subst *,%,$2),$d))
core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1)))))))))))))))))))))))))))
# @todo On Windows: $(shell dir /B $(1)); make sure to handle when no file exists.
core_ls = $(filter-out $(1),$(shell echo $(1)))
# Automated update.
ERLANG_MK_BUILD_CONFIG ?= build.config
ERLANG_MK_BUILD_DIR ?= .erlang.mk.build
erlang-mk:
git clone https://github.com/ninenines/erlang.mk $(ERLANG_MK_BUILD_DIR)
if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR); fi
cd $(ERLANG_MK_BUILD_DIR) && $(MAKE)
cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
rm -rf $(ERLANG_MK_BUILD_DIR)
# The erlang.mk package index is bundled in the default erlang.mk build.
# Search for the string "copyright" to skip to the rest of the code.
PACKAGES += aberth
pkg_aberth_name = aberth
pkg_aberth_description = Generic BERT-RPC server in Erlang
pkg_aberth_homepage = https://github.com/a13x/aberth
pkg_aberth_fetch = git
pkg_aberth_repo = https://github.com/a13x/aberth
pkg_aberth_commit = master
PACKAGES += active
pkg_active_name = active
pkg_active_description = Active development for Erlang: rebuild and reload source/binary files while the VM is running
pkg_active_homepage = https://github.com/proger/active
pkg_active_fetch = git
pkg_active_repo = https://github.com/proger/active
pkg_active_commit = master
PACKAGES += actordb_core
pkg_actordb_core_name = actordb_core
pkg_actordb_core_description = ActorDB main source
pkg_actordb_core_homepage = http://www.actordb.com/
pkg_actordb_core_fetch = git
pkg_actordb_core_repo = https://github.com/biokoda/actordb_core
pkg_actordb_core_commit = master
PACKAGES += actordb_thrift
pkg_actordb_thrift_name = actordb_thrift
pkg_actordb_thrift_description = Thrift API for ActorDB
pkg_actordb_thrift_homepage = http://www.actordb.com/
pkg_actordb_thrift_fetch = git
pkg_actordb_thrift_repo = https://github.com/biokoda/actordb_thrift
pkg_actordb_thrift_commit = master
PACKAGES += aleppo
pkg_aleppo_name = aleppo
pkg_aleppo_description = Alternative Erlang Pre-Processor
pkg_aleppo_homepage = https://github.com/ErlyORM/aleppo
pkg_aleppo_fetch = git
pkg_aleppo_repo = https://github.com/ErlyORM/aleppo
pkg_aleppo_commit = master
PACKAGES += alog
pkg_alog_name = alog
pkg_alog_description = Simply the best logging framework for Erlang
pkg_alog_homepage = https://github.com/siberian-fast-food/alogger
pkg_alog_fetch = git
pkg_alog_repo = https://github.com/siberian-fast-food/alogger
pkg_alog_commit = master
PACKAGES += amqp_client
pkg_amqp_client_name = amqp_client
pkg_amqp_client_description = RabbitMQ Erlang AMQP client
pkg_amqp_client_homepage = https://www.rabbitmq.com/erlang-client-user-guide.html
pkg_amqp_client_fetch = git
pkg_amqp_client_repo = https://github.com/rabbitmq/rabbitmq-erlang-client.git
pkg_amqp_client_commit = master
PACKAGES += annotations
pkg_annotations_name = annotations
pkg_annotations_description = Simple code instrumentation utilities
pkg_annotations_homepage = https://github.com/hyperthunk/annotations
pkg_annotations_fetch = git
pkg_annotations_repo = https://github.com/hyperthunk/annotations
pkg_annotations_commit = master
PACKAGES += antidote
pkg_antidote_name = antidote
pkg_antidote_description = Large-scale computation without synchronisation
pkg_antidote_homepage = https://syncfree.lip6.fr/
pkg_antidote_fetch = git
pkg_antidote_repo = https://github.com/SyncFree/antidote
pkg_antidote_commit = master
PACKAGES += apns
pkg_apns_name = apns
pkg_apns_description = Apple Push Notification Server for Erlang
pkg_apns_homepage = http://inaka.github.com/apns4erl
pkg_apns_fetch = git
pkg_apns_repo = https://github.com/inaka/apns4erl
pkg_apns_commit = 1.0.4
PACKAGES += azdht
pkg_azdht_name = azdht
pkg_azdht_description = Azureus Distributed Hash Table (DHT) in Erlang
pkg_azdht_homepage = https://github.com/arcusfelis/azdht
pkg_azdht_fetch = git
pkg_azdht_repo = https://github.com/arcusfelis/azdht
pkg_azdht_commit = master
PACKAGES += backoff
pkg_backoff_name = backoff
pkg_backoff_description = Simple exponential backoffs in Erlang
pkg_backoff_homepage = https://github.com/ferd/backoff
pkg_backoff_fetch = git
pkg_backoff_repo = https://github.com/ferd/backoff
pkg_backoff_commit = master
PACKAGES += barrel_tcp
pkg_barrel_tcp_name = barrel_tcp
pkg_barrel_tcp_description = barrel is a generic TCP acceptor pool with low latency in Erlang.
pkg_barrel_tcp_homepage = https://github.com/benoitc-attic/barrel_tcp
pkg_barrel_tcp_fetch = git
pkg_barrel_tcp_repo = https://github.com/benoitc-attic/barrel_tcp
pkg_barrel_tcp_commit = master
PACKAGES += basho_bench
pkg_basho_bench_name = basho_bench
pkg_basho_bench_description = A load-generation and testing tool for basically whatever you can write a returning Erlang function for.
pkg_basho_bench_homepage = https://github.com/basho/basho_bench
pkg_basho_bench_fetch = git
pkg_basho_bench_repo = https://github.com/basho/basho_bench
pkg_basho_bench_commit = master
PACKAGES += bcrypt
pkg_bcrypt_name = bcrypt
pkg_bcrypt_description = Bcrypt Erlang / C library
pkg_bcrypt_homepage = https://github.com/riverrun/branglecrypt
pkg_bcrypt_fetch = git
pkg_bcrypt_repo = https://github.com/riverrun/branglecrypt
pkg_bcrypt_commit = master
PACKAGES += beam
pkg_beam_name = beam
pkg_beam_description = BEAM emulator written in Erlang
pkg_beam_homepage = https://github.com/tonyrog/beam
pkg_beam_fetch = git
pkg_beam_repo = https://github.com/tonyrog/beam
pkg_beam_commit = master
PACKAGES += beanstalk
pkg_beanstalk_name = beanstalk
pkg_beanstalk_description = An Erlang client for beanstalkd
pkg_beanstalk_homepage = https://github.com/tim/erlang-beanstalk
pkg_beanstalk_fetch = git
pkg_beanstalk_repo = https://github.com/tim/erlang-beanstalk
pkg_beanstalk_commit = master
PACKAGES += bear
pkg_bear_name = bear
pkg_bear_description = a set of statistics functions for erlang
pkg_bear_homepage = https://github.com/boundary/bear
pkg_bear_fetch = git
pkg_bear_repo = https://github.com/boundary/bear
pkg_bear_commit = master
PACKAGES += bertconf
pkg_bertconf_name = bertconf
pkg_bertconf_description = Make ETS tables out of statc BERT files that are auto-reloaded
pkg_bertconf_homepage = https://github.com/ferd/bertconf
pkg_bertconf_fetch = git
pkg_bertconf_repo = https://github.com/ferd/bertconf
pkg_bertconf_commit = master
PACKAGES += bifrost
pkg_bifrost_name = bifrost
pkg_bifrost_description = Erlang FTP Server Framework
pkg_bifrost_homepage = https://github.com/thorstadt/bifrost
pkg_bifrost_fetch = git
pkg_bifrost_repo = https://github.com/thorstadt/bifrost
pkg_bifrost_commit = master
PACKAGES += binpp
pkg_binpp_name = binpp
pkg_binpp_description = Erlang Binary Pretty Printer
pkg_binpp_homepage = https://github.com/jtendo/binpp
pkg_binpp_fetch = git
pkg_binpp_repo = https://github.com/jtendo/binpp
pkg_binpp_commit = master
PACKAGES += bisect
pkg_bisect_name = bisect
pkg_bisect_description = Ordered fixed-size binary dictionary in Erlang
pkg_bisect_homepage = https://github.com/knutin/bisect
pkg_bisect_fetch = git
pkg_bisect_repo = https://github.com/knutin/bisect
pkg_bisect_commit = master
PACKAGES += bitcask
pkg_bitcask_name = bitcask
pkg_bitcask_description = because you need another a key/value storage engine
pkg_bitcask_homepage = https://github.com/basho/bitcask
pkg_bitcask_fetch = git
pkg_bitcask_repo = https://github.com/basho/bitcask
pkg_bitcask_commit = master
PACKAGES += bitstore
pkg_bitstore_name = bitstore
pkg_bitstore_description = A document based ontology development environment
pkg_bitstore_homepage = https://github.com/bdionne/bitstore
pkg_bitstore_fetch = git
pkg_bitstore_repo = https://github.com/bdionne/bitstore
pkg_bitstore_commit = master
PACKAGES += bootstrap
pkg_bootstrap_name = bootstrap
pkg_bootstrap_description = A simple, yet powerful Erlang cluster bootstrapping application.
pkg_bootstrap_homepage = https://github.com/schlagert/bootstrap
pkg_bootstrap_fetch = git
pkg_bootstrap_repo = https://github.com/schlagert/bootstrap
pkg_bootstrap_commit = master
PACKAGES += boss_db
pkg_boss_db_name = boss_db
pkg_boss_db_description = BossDB: a sharded, caching, pooling, evented ORM for Erlang
pkg_boss_db_homepage = https://github.com/ErlyORM/boss_db
pkg_boss_db_fetch = git
pkg_boss_db_repo = https://github.com/ErlyORM/boss_db
pkg_boss_db_commit = master
PACKAGES += boss
pkg_boss_name = boss
pkg_boss_description = Erlang web MVC, now featuring Comet
pkg_boss_homepage = https://github.com/ChicagoBoss/ChicagoBoss
pkg_boss_fetch = git
pkg_boss_repo = https://github.com/ChicagoBoss/ChicagoBoss
pkg_boss_commit = master
PACKAGES += bson
pkg_bson_name = bson
pkg_bson_description = BSON documents in Erlang, see bsonspec.org
pkg_bson_homepage = https://github.com/comtihon/bson-erlang
pkg_bson_fetch = git
pkg_bson_repo = https://github.com/comtihon/bson-erlang
pkg_bson_commit = master
PACKAGES += bullet
pkg_bullet_name = bullet
pkg_bullet_description = Simple, reliable, efficient streaming for Cowboy.
pkg_bullet_homepage = http://ninenines.eu
pkg_bullet_fetch = git
pkg_bullet_repo = https://github.com/extend/bullet
pkg_bullet_commit = master
PACKAGES += cache
pkg_cache_name = cache
pkg_cache_description = Erlang in-memory cache
pkg_cache_homepage = https://github.com/fogfish/cache
pkg_cache_fetch = git
pkg_cache_repo = https://github.com/fogfish/cache
pkg_cache_commit = master
PACKAGES += cake
pkg_cake_name = cake
pkg_cake_description = Really simple terminal colorization
pkg_cake_homepage = https://github.com/darach/cake-erl
pkg_cake_fetch = git
pkg_cake_repo = https://github.com/darach/cake-erl
pkg_cake_commit = v0.1.2
PACKAGES += carotene
pkg_carotene_name = carotene
pkg_carotene_description = Real-time server
pkg_carotene_homepage = https://github.com/carotene/carotene
pkg_carotene_fetch = git
pkg_carotene_repo = https://github.com/carotene/carotene
pkg_carotene_commit = master
PACKAGES += cberl
pkg_cberl_name = cberl
pkg_cberl_description = NIF based Erlang bindings for Couchbase
pkg_cberl_homepage = https://github.com/chitika/cberl
pkg_cberl_fetch = git
pkg_cberl_repo = https://github.com/chitika/cberl
pkg_cberl_commit = master
PACKAGES += cecho
pkg_cecho_name = cecho
pkg_cecho_description = An ncurses library for Erlang
pkg_cecho_homepage = https://github.com/mazenharake/cecho
pkg_cecho_fetch = git
pkg_cecho_repo = https://github.com/mazenharake/cecho
pkg_cecho_commit = master
PACKAGES += cferl
pkg_cferl_name = cferl
pkg_cferl_description = Rackspace / Open Stack Cloud Files Erlang Client
pkg_cferl_homepage = https://github.com/ddossot/cferl
pkg_cferl_fetch = git
pkg_cferl_repo = https://github.com/ddossot/cferl
pkg_cferl_commit = master
PACKAGES += chaos_monkey
pkg_chaos_monkey_name = chaos_monkey
pkg_chaos_monkey_description = This is The CHAOS MONKEY. It will kill your processes.
pkg_chaos_monkey_homepage = https://github.com/dLuna/chaos_monkey
pkg_chaos_monkey_fetch = git
pkg_chaos_monkey_repo = https://github.com/dLuna/chaos_monkey
pkg_chaos_monkey_commit = master
PACKAGES += check_node
pkg_check_node_name = check_node
pkg_check_node_description = Nagios Scripts for monitoring Riak
pkg_check_node_homepage = https://github.com/basho-labs/riak_nagios
pkg_check_node_fetch = git
pkg_check_node_repo = https://github.com/basho-labs/riak_nagios
pkg_check_node_commit = master
PACKAGES += chronos
pkg_chronos_name = chronos
pkg_chronos_description = Timer module for Erlang that makes it easy to abstact time out of the tests.
pkg_chronos_homepage = https://github.com/lehoff/chronos
pkg_chronos_fetch = git
pkg_chronos_repo = https://github.com/lehoff/chronos
pkg_chronos_commit = master
PACKAGES += classifier
pkg_classifier_name = classifier
pkg_classifier_description = An Erlang Bayesian Filter and Text Classifier
pkg_classifier_homepage = https://github.com/inaka/classifier
pkg_classifier_fetch = git
pkg_classifier_repo = https://github.com/inaka/classifier
pkg_classifier_commit = master
PACKAGES += clique
pkg_clique_name = clique
pkg_clique_description = CLI Framework for Erlang
pkg_clique_homepage = https://github.com/basho/clique
pkg_clique_fetch = git
pkg_clique_repo = https://github.com/basho/clique
pkg_clique_commit = develop
PACKAGES += cl
pkg_cl_name = cl
pkg_cl_description = OpenCL binding for Erlang
pkg_cl_homepage = https://github.com/tonyrog/cl
pkg_cl_fetch = git
pkg_cl_repo = https://github.com/tonyrog/cl
pkg_cl_commit = master
PACKAGES += cloudi_core
pkg_cloudi_core_name = cloudi_core
pkg_cloudi_core_description = CloudI internal service runtime
pkg_cloudi_core_homepage = http://cloudi.org/
pkg_cloudi_core_fetch = git
pkg_cloudi_core_repo = https://github.com/CloudI/cloudi_core
pkg_cloudi_core_commit = master
PACKAGES += cloudi_service_api_requests
pkg_cloudi_service_api_requests_name = cloudi_service_api_requests
pkg_cloudi_service_api_requests_description = CloudI Service API requests (JSON-RPC/Erlang-term support)
pkg_cloudi_service_api_requests_homepage = http://cloudi.org/
pkg_cloudi_service_api_requests_fetch = git
pkg_cloudi_service_api_requests_repo = https://github.com/CloudI/cloudi_service_api_requests
pkg_cloudi_service_api_requests_commit = master
PACKAGES += cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_name = cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_description = Cassandra CQL CloudI Service
pkg_cloudi_service_db_cassandra_cql_homepage = http://cloudi.org/
pkg_cloudi_service_db_cassandra_cql_fetch = git
pkg_cloudi_service_db_cassandra_cql_repo = https://github.com/CloudI/cloudi_service_db_cassandra_cql
pkg_cloudi_service_db_cassandra_cql_commit = master
PACKAGES += cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_name = cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_description = Cassandra CloudI Service
pkg_cloudi_service_db_cassandra_homepage = http://cloudi.org/
pkg_cloudi_service_db_cassandra_fetch = git
pkg_cloudi_service_db_cassandra_repo = https://github.com/CloudI/cloudi_service_db_cassandra
pkg_cloudi_service_db_cassandra_commit = master
PACKAGES += cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_name = cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_description = CouchDB CloudI Service
pkg_cloudi_service_db_couchdb_homepage = http://cloudi.org/
pkg_cloudi_service_db_couchdb_fetch = git
pkg_cloudi_service_db_couchdb_repo = https://github.com/CloudI/cloudi_service_db_couchdb
pkg_cloudi_service_db_couchdb_commit = master
PACKAGES += cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_name = cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_description = elasticsearch CloudI Service
pkg_cloudi_service_db_elasticsearch_homepage = http://cloudi.org/
pkg_cloudi_service_db_elasticsearch_fetch = git
pkg_cloudi_service_db_elasticsearch_repo = https://github.com/CloudI/cloudi_service_db_elasticsearch
pkg_cloudi_service_db_elasticsearch_commit = master
PACKAGES += cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_name = cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_description = memcached CloudI Service
pkg_cloudi_service_db_memcached_homepage = http://cloudi.org/
pkg_cloudi_service_db_memcached_fetch = git
pkg_cloudi_service_db_memcached_repo = https://github.com/CloudI/cloudi_service_db_memcached
pkg_cloudi_service_db_memcached_commit = master
PACKAGES += cloudi_service_db
pkg_cloudi_service_db_name = cloudi_service_db
pkg_cloudi_service_db_description = CloudI Database (in-memory/testing/generic)
pkg_cloudi_service_db_homepage = http://cloudi.org/
pkg_cloudi_service_db_fetch = git
pkg_cloudi_service_db_repo = https://github.com/CloudI/cloudi_service_db
pkg_cloudi_service_db_commit = master
PACKAGES += cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_name = cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_description = MySQL CloudI Service
pkg_cloudi_service_db_mysql_homepage = http://cloudi.org/
pkg_cloudi_service_db_mysql_fetch = git
pkg_cloudi_service_db_mysql_repo = https://github.com/CloudI/cloudi_service_db_mysql
pkg_cloudi_service_db_mysql_commit = master
PACKAGES += cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_name = cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_description = PostgreSQL CloudI Service
pkg_cloudi_service_db_pgsql_homepage = http://cloudi.org/
pkg_cloudi_service_db_pgsql_fetch = git
pkg_cloudi_service_db_pgsql_repo = https://github.com/CloudI/cloudi_service_db_pgsql
pkg_cloudi_service_db_pgsql_commit = master
PACKAGES += cloudi_service_db_riak
pkg_cloudi_service_db_riak_name = cloudi_service_db_riak
pkg_cloudi_service_db_riak_description = Riak CloudI Service
pkg_cloudi_service_db_riak_homepage = http://cloudi.org/
pkg_cloudi_service_db_riak_fetch = git
pkg_cloudi_service_db_riak_repo = https://github.com/CloudI/cloudi_service_db_riak
pkg_cloudi_service_db_riak_commit = master
PACKAGES += cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_name = cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_description = Tokyo Tyrant CloudI Service
pkg_cloudi_service_db_tokyotyrant_homepage = http://cloudi.org/
pkg_cloudi_service_db_tokyotyrant_fetch = git
pkg_cloudi_service_db_tokyotyrant_repo = https://github.com/CloudI/cloudi_service_db_tokyotyrant
pkg_cloudi_service_db_tokyotyrant_commit = master
PACKAGES += cloudi_service_filesystem
pkg_cloudi_service_filesystem_name = cloudi_service_filesystem
pkg_cloudi_service_filesystem_description = Filesystem CloudI Service
pkg_cloudi_service_filesystem_homepage = http://cloudi.org/
pkg_cloudi_service_filesystem_fetch = git
pkg_cloudi_service_filesystem_repo = https://github.com/CloudI/cloudi_service_filesystem
pkg_cloudi_service_filesystem_commit = master
PACKAGES += cloudi_service_http_client
pkg_cloudi_service_http_client_name = cloudi_service_http_client
pkg_cloudi_service_http_client_description = HTTP client CloudI Service
pkg_cloudi_service_http_client_homepage = http://cloudi.org/
pkg_cloudi_service_http_client_fetch = git
pkg_cloudi_service_http_client_repo = https://github.com/CloudI/cloudi_service_http_client
pkg_cloudi_service_http_client_commit = master
PACKAGES += cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_name = cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_description = cowboy HTTP/HTTPS CloudI Service
pkg_cloudi_service_http_cowboy_homepage = http://cloudi.org/
pkg_cloudi_service_http_cowboy_fetch = git
pkg_cloudi_service_http_cowboy_repo = https://github.com/CloudI/cloudi_service_http_cowboy
pkg_cloudi_service_http_cowboy_commit = master
PACKAGES += cloudi_service_http_elli
pkg_cloudi_service_http_elli_name = cloudi_service_http_elli
pkg_cloudi_service_http_elli_description = elli HTTP CloudI Service
pkg_cloudi_service_http_elli_homepage = http://cloudi.org/
pkg_cloudi_service_http_elli_fetch = git
pkg_cloudi_service_http_elli_repo = https://github.com/CloudI/cloudi_service_http_elli
pkg_cloudi_service_http_elli_commit = master
PACKAGES += cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_name = cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_description = Map/Reduce CloudI Service
pkg_cloudi_service_map_reduce_homepage = http://cloudi.org/
pkg_cloudi_service_map_reduce_fetch = git
pkg_cloudi_service_map_reduce_repo = https://github.com/CloudI/cloudi_service_map_reduce
pkg_cloudi_service_map_reduce_commit = master
PACKAGES += cloudi_service_oauth1
pkg_cloudi_service_oauth1_name = cloudi_service_oauth1
pkg_cloudi_service_oauth1_description = OAuth v1.0 CloudI Service
pkg_cloudi_service_oauth1_homepage = http://cloudi.org/
pkg_cloudi_service_oauth1_fetch = git
pkg_cloudi_service_oauth1_repo = https://github.com/CloudI/cloudi_service_oauth1
pkg_cloudi_service_oauth1_commit = master
PACKAGES += cloudi_service_queue
pkg_cloudi_service_queue_name = cloudi_service_queue
pkg_cloudi_service_queue_description = Persistent Queue Service
pkg_cloudi_service_queue_homepage = http://cloudi.org/
pkg_cloudi_service_queue_fetch = git
pkg_cloudi_service_queue_repo = https://github.com/CloudI/cloudi_service_queue
pkg_cloudi_service_queue_commit = master
PACKAGES += cloudi_service_quorum
pkg_cloudi_service_quorum_name = cloudi_service_quorum
pkg_cloudi_service_quorum_description = CloudI Quorum Service
pkg_cloudi_service_quorum_homepage = http://cloudi.org/
pkg_cloudi_service_quorum_fetch = git
pkg_cloudi_service_quorum_repo = https://github.com/CloudI/cloudi_service_quorum
pkg_cloudi_service_quorum_commit = master
PACKAGES += cloudi_service_router
pkg_cloudi_service_router_name = cloudi_service_router
pkg_cloudi_service_router_description = CloudI Router Service
pkg_cloudi_service_router_homepage = http://cloudi.org/
pkg_cloudi_service_router_fetch = git
pkg_cloudi_service_router_repo = https://github.com/CloudI/cloudi_service_router
pkg_cloudi_service_router_commit = master
PACKAGES += cloudi_service_tcp
pkg_cloudi_service_tcp_name = cloudi_service_tcp
pkg_cloudi_service_tcp_description = TCP CloudI Service
pkg_cloudi_service_tcp_homepage = http://cloudi.org/
pkg_cloudi_service_tcp_fetch = git
pkg_cloudi_service_tcp_repo = https://github.com/CloudI/cloudi_service_tcp
pkg_cloudi_service_tcp_commit = master
PACKAGES += cloudi_service_timers
pkg_cloudi_service_timers_name = cloudi_service_timers
pkg_cloudi_service_timers_description = Timers CloudI Service
pkg_cloudi_service_timers_homepage = http://cloudi.org/
pkg_cloudi_service_timers_fetch = git
pkg_cloudi_service_timers_repo = https://github.com/CloudI/cloudi_service_timers
pkg_cloudi_service_timers_commit = master
PACKAGES += cloudi_service_udp
pkg_cloudi_service_udp_name = cloudi_service_udp
pkg_cloudi_service_udp_description = UDP CloudI Service
pkg_cloudi_service_udp_homepage = http://cloudi.org/
pkg_cloudi_service_udp_fetch = git
pkg_cloudi_service_udp_repo = https://github.com/CloudI/cloudi_service_udp
pkg_cloudi_service_udp_commit = master
PACKAGES += cloudi_service_validate
pkg_cloudi_service_validate_name = cloudi_service_validate
pkg_cloudi_service_validate_description = CloudI Validate Service
pkg_cloudi_service_validate_homepage = http://cloudi.org/
pkg_cloudi_service_validate_fetch = git
pkg_cloudi_service_validate_repo = https://github.com/CloudI/cloudi_service_validate
pkg_cloudi_service_validate_commit = master
PACKAGES += cloudi_service_zeromq
pkg_cloudi_service_zeromq_name = cloudi_service_zeromq
pkg_cloudi_service_zeromq_description = ZeroMQ CloudI Service
pkg_cloudi_service_zeromq_homepage = http://cloudi.org/
pkg_cloudi_service_zeromq_fetch = git
pkg_cloudi_service_zeromq_repo = https://github.com/CloudI/cloudi_service_zeromq
pkg_cloudi_service_zeromq_commit = master
PACKAGES += cluster_info
pkg_cluster_info_name = cluster_info
pkg_cluster_info_description = Fork of Hibari's nifty cluster_info OTP app
pkg_cluster_info_homepage = https://github.com/basho/cluster_info
pkg_cluster_info_fetch = git
pkg_cluster_info_repo = https://github.com/basho/cluster_info
pkg_cluster_info_commit = master
PACKAGES += color
pkg_color_name = color
pkg_color_description = ANSI colors for your Erlang
pkg_color_homepage = https://github.com/julianduque/erlang-color
pkg_color_fetch = git
pkg_color_repo = https://github.com/julianduque/erlang-color
pkg_color_commit = master
PACKAGES += confetti
pkg_confetti_name = confetti
pkg_confetti_description = Erlang configuration provider / application:get_env/2 on steroids
pkg_confetti_homepage = https://github.com/jtendo/confetti
pkg_confetti_fetch = git
pkg_confetti_repo = https://github.com/jtendo/confetti
pkg_confetti_commit = master
PACKAGES += couchbeam
pkg_couchbeam_name = couchbeam
pkg_couchbeam_description = Apache CouchDB client in Erlang
pkg_couchbeam_homepage = https://github.com/benoitc/couchbeam
pkg_couchbeam_fetch = git
pkg_couchbeam_repo = https://github.com/benoitc/couchbeam
pkg_couchbeam_commit = master
PACKAGES += couch
pkg_couch_name = couch
pkg_couch_description = A embeddable document oriented database compatible with Apache CouchDB
pkg_couch_homepage = https://github.com/benoitc/opencouch
pkg_couch_fetch = git
pkg_couch_repo = https://github.com/benoitc/opencouch
pkg_couch_commit = master
PACKAGES += covertool
pkg_covertool_name = covertool
pkg_covertool_description = Tool to convert Erlang cover data files into Cobertura XML reports
pkg_covertool_homepage = https://github.com/idubrov/covertool
pkg_covertool_fetch = git
pkg_covertool_repo = https://github.com/idubrov/covertool
pkg_covertool_commit = master
PACKAGES += cowboy
pkg_cowboy_name = cowboy
pkg_cowboy_description = Small, fast and modular HTTP server.
pkg_cowboy_homepage = http://ninenines.eu
pkg_cowboy_fetch = git
pkg_cowboy_repo = https://github.com/ninenines/cowboy
pkg_cowboy_commit = 1.0.1
PACKAGES += cowdb
pkg_cowdb_name = cowdb
pkg_cowdb_description = Pure Key/Value database library for Erlang Applications
pkg_cowdb_homepage = https://github.com/refuge/cowdb
pkg_cowdb_fetch = git
pkg_cowdb_repo = https://github.com/refuge/cowdb
pkg_cowdb_commit = master
PACKAGES += cowlib
pkg_cowlib_name = cowlib
pkg_cowlib_description = Support library for manipulating Web protocols.
pkg_cowlib_homepage = http://ninenines.eu
pkg_cowlib_fetch = git
pkg_cowlib_repo = https://github.com/ninenines/cowlib
pkg_cowlib_commit = 1.0.1
PACKAGES += cpg
pkg_cpg_name = cpg
pkg_cpg_description = CloudI Process Groups
pkg_cpg_homepage = https://github.com/okeuday/cpg
pkg_cpg_fetch = git
pkg_cpg_repo = https://github.com/okeuday/cpg
pkg_cpg_commit = master
PACKAGES += cqerl
pkg_cqerl_name = cqerl
pkg_cqerl_description = Native Erlang CQL client for Cassandra
pkg_cqerl_homepage = https://matehat.github.io/cqerl/
pkg_cqerl_fetch = git
pkg_cqerl_repo = https://github.com/matehat/cqerl
pkg_cqerl_commit = master
PACKAGES += cr
pkg_cr_name = cr
pkg_cr_description = Chain Replication
pkg_cr_homepage = https://synrc.com/apps/cr/doc/cr.htm
pkg_cr_fetch = git
pkg_cr_repo = https://github.com/spawnproc/cr
pkg_cr_commit = master
PACKAGES += cuttlefish
pkg_cuttlefish_name = cuttlefish
pkg_cuttlefish_description = never lose your childlike sense of wonder baby cuttlefish, promise me?
pkg_cuttlefish_homepage = https://github.com/basho/cuttlefish
pkg_cuttlefish_fetch = git
pkg_cuttlefish_repo = https://github.com/basho/cuttlefish
pkg_cuttlefish_commit = master
PACKAGES += damocles
pkg_damocles_name = damocles
pkg_damocles_description = Erlang library for generating adversarial network conditions for QAing distributed applications/systems on a single Linux box.
pkg_damocles_homepage = https://github.com/lostcolony/damocles
pkg_damocles_fetch = git
pkg_damocles_repo = https://github.com/lostcolony/damocles
pkg_damocles_commit = master
PACKAGES += debbie
pkg_debbie_name = debbie
pkg_debbie_description = .DEB Built In Erlang
pkg_debbie_homepage = https://github.com/crownedgrouse/debbie
pkg_debbie_fetch = git
pkg_debbie_repo = https://github.com/crownedgrouse/debbie
pkg_debbie_commit = master
PACKAGES += decimal
pkg_decimal_name = decimal
pkg_decimal_description = An Erlang decimal arithmetic library
pkg_decimal_homepage = https://github.com/tim/erlang-decimal
pkg_decimal_fetch = git
pkg_decimal_repo = https://github.com/tim/erlang-decimal
pkg_decimal_commit = master
PACKAGES += detergent
pkg_detergent_name = detergent
pkg_detergent_description = An emulsifying Erlang SOAP library
pkg_detergent_homepage = https://github.com/devinus/detergent
pkg_detergent_fetch = git
pkg_detergent_repo = https://github.com/devinus/detergent
pkg_detergent_commit = master
PACKAGES += detest
pkg_detest_name = detest
pkg_detest_description = Tool for running tests on a cluster of erlang nodes
pkg_detest_homepage = https://github.com/biokoda/detest
pkg_detest_fetch = git
pkg_detest_repo = https://github.com/biokoda/detest
pkg_detest_commit = master
PACKAGES += dh_date
pkg_dh_date_name = dh_date
pkg_dh_date_description = Date formatting / parsing library for erlang
pkg_dh_date_homepage = https://github.com/daleharvey/dh_date
pkg_dh_date_fetch = git
pkg_dh_date_repo = https://github.com/daleharvey/dh_date
pkg_dh_date_commit = master
PACKAGES += dhtcrawler
pkg_dhtcrawler_name = dhtcrawler
pkg_dhtcrawler_description = dhtcrawler is a DHT crawler written in erlang. It can join a DHT network and crawl many P2P torrents.
pkg_dhtcrawler_homepage = https://github.com/kevinlynx/dhtcrawler
pkg_dhtcrawler_fetch = git
pkg_dhtcrawler_repo = https://github.com/kevinlynx/dhtcrawler
pkg_dhtcrawler_commit = master
PACKAGES += dirbusterl
pkg_dirbusterl_name = dirbusterl
pkg_dirbusterl_description = DirBuster successor in Erlang
pkg_dirbusterl_homepage = https://github.com/silentsignal/DirBustErl
pkg_dirbusterl_fetch = git
pkg_dirbusterl_repo = https://github.com/silentsignal/DirBustErl
pkg_dirbusterl_commit = master
PACKAGES += dispcount
pkg_dispcount_name = dispcount
pkg_dispcount_description = Erlang task dispatcher based on ETS counters.
pkg_dispcount_homepage = https://github.com/ferd/dispcount
pkg_dispcount_fetch = git
pkg_dispcount_repo = https://github.com/ferd/dispcount
pkg_dispcount_commit = master
PACKAGES += dlhttpc
pkg_dlhttpc_name = dlhttpc
pkg_dlhttpc_description = dispcount-based lhttpc fork for massive amounts of requests to limited endpoints
pkg_dlhttpc_homepage = https://github.com/ferd/dlhttpc
pkg_dlhttpc_fetch = git
pkg_dlhttpc_repo = https://github.com/ferd/dlhttpc
pkg_dlhttpc_commit = master
PACKAGES += dns
pkg_dns_name = dns
pkg_dns_description = Erlang DNS library
pkg_dns_homepage = https://github.com/aetrion/dns_erlang
pkg_dns_fetch = git
pkg_dns_repo = https://github.com/aetrion/dns_erlang
pkg_dns_commit = master
PACKAGES += dnssd
pkg_dnssd_name = dnssd
pkg_dnssd_description = Erlang interface to Apple's Bonjour D NS Service Discovery implementation
pkg_dnssd_homepage = https://github.com/benoitc/dnssd_erlang
pkg_dnssd_fetch = git
pkg_dnssd_repo = https://github.com/benoitc/dnssd_erlang
pkg_dnssd_commit = master
PACKAGES += dtl
pkg_dtl_name = dtl
pkg_dtl_description = Django Template Language: A full-featured port of the Django template engine to Erlang.
pkg_dtl_homepage = https://github.com/oinksoft/dtl
pkg_dtl_fetch = git
pkg_dtl_repo = https://github.com/oinksoft/dtl
pkg_dtl_commit = master
PACKAGES += dynamic_compile
pkg_dynamic_compile_name = dynamic_compile
pkg_dynamic_compile_description = compile and load erlang modules from string input
pkg_dynamic_compile_homepage = https://github.com/jkvor/dynamic_compile
pkg_dynamic_compile_fetch = git
pkg_dynamic_compile_repo = https://github.com/jkvor/dynamic_compile
pkg_dynamic_compile_commit = master
PACKAGES += e2
pkg_e2_name = e2
pkg_e2_description = Library to simply writing correct OTP applications.
pkg_e2_homepage = http://e2project.org
pkg_e2_fetch = git
pkg_e2_repo = https://github.com/gar1t/e2
pkg_e2_commit = master
PACKAGES += eamf
pkg_eamf_name = eamf
pkg_eamf_description = eAMF provides Action Message Format (AMF) support for Erlang
pkg_eamf_homepage = https://github.com/mrinalwadhwa/eamf
pkg_eamf_fetch = git
pkg_eamf_repo = https://github.com/mrinalwadhwa/eamf
pkg_eamf_commit = master
PACKAGES += eavro
pkg_eavro_name = eavro
pkg_eavro_description = Apache Avro encoder/decoder
pkg_eavro_homepage = https://github.com/SIfoxDevTeam/eavro
pkg_eavro_fetch = git
pkg_eavro_repo = https://github.com/SIfoxDevTeam/eavro
pkg_eavro_commit = master
PACKAGES += ecapnp
pkg_ecapnp_name = ecapnp
pkg_ecapnp_description = Cap'n Proto library for Erlang
pkg_ecapnp_homepage = https://github.com/kaos/ecapnp
pkg_ecapnp_fetch = git
pkg_ecapnp_repo = https://github.com/kaos/ecapnp
pkg_ecapnp_commit = master
PACKAGES += econfig
pkg_econfig_name = econfig
pkg_econfig_description = simple Erlang config handler using INI files
pkg_econfig_homepage = https://github.com/benoitc/econfig
pkg_econfig_fetch = git
pkg_econfig_repo = https://github.com/benoitc/econfig
pkg_econfig_commit = master
PACKAGES += edate
pkg_edate_name = edate
pkg_edate_description = date manipulation library for erlang
pkg_edate_homepage = https://github.com/dweldon/edate
pkg_edate_fetch = git
pkg_edate_repo = https://github.com/dweldon/edate
pkg_edate_commit = master
PACKAGES += edgar
pkg_edgar_name = edgar
pkg_edgar_description = Erlang Does GNU AR
pkg_edgar_homepage = https://github.com/crownedgrouse/edgar
pkg_edgar_fetch = git
pkg_edgar_repo = https://github.com/crownedgrouse/edgar
pkg_edgar_commit = master
PACKAGES += edis
pkg_edis_name = edis
pkg_edis_description = An Erlang implementation of Redis KV Store
pkg_edis_homepage = http://inaka.github.com/edis/
pkg_edis_fetch = git
pkg_edis_repo = https://github.com/inaka/edis
pkg_edis_commit = master
PACKAGES += edns
pkg_edns_name = edns
pkg_edns_description = Erlang/OTP DNS server
pkg_edns_homepage = https://github.com/hcvst/erlang-dns
pkg_edns_fetch = git
pkg_edns_repo = https://github.com/hcvst/erlang-dns
pkg_edns_commit = master
PACKAGES += edown
pkg_edown_name = edown
pkg_edown_description = EDoc extension for generating Github-flavored Markdown
pkg_edown_homepage = https://github.com/uwiger/edown
pkg_edown_fetch = git
pkg_edown_repo = https://github.com/uwiger/edown
pkg_edown_commit = master
PACKAGES += eep_app
pkg_eep_app_name = eep_app
pkg_eep_app_description = Embedded Event Processing
pkg_eep_app_homepage = https://github.com/darach/eep-erl
pkg_eep_app_fetch = git
pkg_eep_app_repo = https://github.com/darach/eep-erl
pkg_eep_app_commit = master
PACKAGES += eep
pkg_eep_name = eep
pkg_eep_description = Erlang Easy Profiling (eep) application provides a way to analyze application performance and call hierarchy
pkg_eep_homepage = https://github.com/virtan/eep
pkg_eep_fetch = git
pkg_eep_repo = https://github.com/virtan/eep
pkg_eep_commit = master
PACKAGES += efene
pkg_efene_name = efene
pkg_efene_description = Alternative syntax for the Erlang Programming Language focusing on simplicity, ease of use and programmer UX
pkg_efene_homepage = https://github.com/efene/efene
pkg_efene_fetch = git
pkg_efene_repo = https://github.com/efene/efene
pkg_efene_commit = master
PACKAGES += eganglia
pkg_eganglia_name = eganglia
pkg_eganglia_description = Erlang library to interact with Ganglia
pkg_eganglia_homepage = https://github.com/inaka/eganglia
pkg_eganglia_fetch = git
pkg_eganglia_repo = https://github.com/inaka/eganglia
pkg_eganglia_commit = v0.9.1
PACKAGES += egeoip
pkg_egeoip_name = egeoip
pkg_egeoip_description = Erlang IP Geolocation module, currently supporting the MaxMind GeoLite City Database.
pkg_egeoip_homepage = https://github.com/mochi/egeoip
pkg_egeoip_fetch = git
pkg_egeoip_repo = https://github.com/mochi/egeoip
pkg_egeoip_commit = master
PACKAGES += ehsa
pkg_ehsa_name = ehsa
pkg_ehsa_description = Erlang HTTP server basic and digest authentication modules
pkg_ehsa_homepage = https://bitbucket.org/a12n/ehsa
pkg_ehsa_fetch = hg
pkg_ehsa_repo = https://bitbucket.org/a12n/ehsa
pkg_ehsa_commit = 2.0.4
PACKAGES += ejabberd
pkg_ejabberd_name = ejabberd
pkg_ejabberd_description = Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform
pkg_ejabberd_homepage = https://github.com/processone/ejabberd
pkg_ejabberd_fetch = git
pkg_ejabberd_repo = https://github.com/processone/ejabberd
pkg_ejabberd_commit = master
PACKAGES += ej
pkg_ej_name = ej
pkg_ej_description = Helper module for working with Erlang terms representing JSON
pkg_ej_homepage = https://github.com/seth/ej
pkg_ej_fetch = git
pkg_ej_repo = https://github.com/seth/ej
pkg_ej_commit = master
PACKAGES += ejwt
pkg_ejwt_name = ejwt
pkg_ejwt_description = erlang library for JSON Web Token
pkg_ejwt_homepage = https://github.com/artefactop/ejwt
pkg_ejwt_fetch = git
pkg_ejwt_repo = https://github.com/artefactop/ejwt
pkg_ejwt_commit = master
PACKAGES += ekaf
pkg_ekaf_name = ekaf
pkg_ekaf_description = A minimal, high-performance Kafka client in Erlang.
pkg_ekaf_homepage = https://github.com/helpshift/ekaf
pkg_ekaf_fetch = git
pkg_ekaf_repo = https://github.com/helpshift/ekaf
pkg_ekaf_commit = master
PACKAGES += elarm
pkg_elarm_name = elarm
pkg_elarm_description = Alarm Manager for Erlang.
pkg_elarm_homepage = https://github.com/esl/elarm
pkg_elarm_fetch = git
pkg_elarm_repo = https://github.com/esl/elarm
pkg_elarm_commit = master
PACKAGES += eleveldb
pkg_eleveldb_name = eleveldb
pkg_eleveldb_description = Erlang LevelDB API
pkg_eleveldb_homepage = https://github.com/basho/eleveldb
pkg_eleveldb_fetch = git
pkg_eleveldb_repo = https://github.com/basho/eleveldb
pkg_eleveldb_commit = master
PACKAGES += elli
pkg_elli_name = elli
pkg_elli_description = Simple, robust and performant Erlang web server
pkg_elli_homepage = https://github.com/knutin/elli
pkg_elli_fetch = git
pkg_elli_repo = https://github.com/knutin/elli
pkg_elli_commit = master
PACKAGES += elvis
pkg_elvis_name = elvis
pkg_elvis_description = Erlang Style Reviewer
pkg_elvis_homepage = https://github.com/inaka/elvis
pkg_elvis_fetch = git
pkg_elvis_repo = https://github.com/inaka/elvis
pkg_elvis_commit = 0.2.4
PACKAGES += emagick
pkg_emagick_name = emagick
pkg_emagick_description = Wrapper for Graphics/ImageMagick command line tool.
pkg_emagick_homepage = https://github.com/kivra/emagick
pkg_emagick_fetch = git
pkg_emagick_repo = https://github.com/kivra/emagick
pkg_emagick_commit = master
PACKAGES += emysql
pkg_emysql_name = emysql
pkg_emysql_description = Stable, pure Erlang MySQL driver.
pkg_emysql_homepage = https://github.com/Eonblast/Emysql
pkg_emysql_fetch = git
pkg_emysql_repo = https://github.com/Eonblast/Emysql
pkg_emysql_commit = master
PACKAGES += enm
pkg_enm_name = enm
pkg_enm_description = Erlang driver for nanomsg
pkg_enm_homepage = https://github.com/basho/enm
pkg_enm_fetch = git
pkg_enm_repo = https://github.com/basho/enm
pkg_enm_commit = master
PACKAGES += entop
pkg_entop_name = entop
pkg_entop_description = A top-like tool for monitoring an Erlang node
pkg_entop_homepage = https://github.com/mazenharake/entop
pkg_entop_fetch = git
pkg_entop_repo = https://github.com/mazenharake/entop
pkg_entop_commit = master
PACKAGES += epcap
pkg_epcap_name = epcap
pkg_epcap_description = Erlang packet capture interface using pcap
pkg_epcap_homepage = https://github.com/msantos/epcap
pkg_epcap_fetch = git
pkg_epcap_repo = https://github.com/msantos/epcap
pkg_epcap_commit = master
PACKAGES += eper
pkg_eper_name = eper
pkg_eper_description = Erlang performance and debugging tools.
pkg_eper_homepage = https://github.com/massemanet/eper
pkg_eper_fetch = git
pkg_eper_repo = https://github.com/massemanet/eper
pkg_eper_commit = master
PACKAGES += epgsql
pkg_epgsql_name = epgsql
pkg_epgsql_description = Erlang PostgreSQL client library.
pkg_epgsql_homepage = https://github.com/epgsql/epgsql
pkg_epgsql_fetch = git
pkg_epgsql_repo = https://github.com/epgsql/epgsql
pkg_epgsql_commit = master
PACKAGES += episcina
pkg_episcina_name = episcina
pkg_episcina_description = A simple non intrusive resource pool for connections
pkg_episcina_homepage = https://github.com/erlware/episcina
pkg_episcina_fetch = git
pkg_episcina_repo = https://github.com/erlware/episcina
pkg_episcina_commit = master
PACKAGES += eplot
pkg_eplot_name = eplot
pkg_eplot_description = A plot engine written in erlang.
pkg_eplot_homepage = https://github.com/psyeugenic/eplot
pkg_eplot_fetch = git
pkg_eplot_repo = https://github.com/psyeugenic/eplot
pkg_eplot_commit = master
PACKAGES += epocxy
pkg_epocxy_name = epocxy
pkg_epocxy_description = Erlang Patterns of Concurrency
pkg_epocxy_homepage = https://github.com/duomark/epocxy
pkg_epocxy_fetch = git
pkg_epocxy_repo = https://github.com/duomark/epocxy
pkg_epocxy_commit = master
PACKAGES += epubnub
pkg_epubnub_name = epubnub
pkg_epubnub_description = Erlang PubNub API
pkg_epubnub_homepage = https://github.com/tsloughter/epubnub
pkg_epubnub_fetch = git
pkg_epubnub_repo = https://github.com/tsloughter/epubnub
pkg_epubnub_commit = master
PACKAGES += eqm
pkg_eqm_name = eqm
pkg_eqm_description = Erlang pub sub with supply-demand channels
pkg_eqm_homepage = https://github.com/loucash/eqm
pkg_eqm_fetch = git
pkg_eqm_repo = https://github.com/loucash/eqm
pkg_eqm_commit = master
PACKAGES += eredis
pkg_eredis_name = eredis
pkg_eredis_description = Erlang Redis client
pkg_eredis_homepage = https://github.com/wooga/eredis
pkg_eredis_fetch = git
pkg_eredis_repo = https://github.com/wooga/eredis
pkg_eredis_commit = master
PACKAGES += eredis_pool
pkg_eredis_pool_name = eredis_pool
pkg_eredis_pool_description = eredis_pool is Pool of Redis clients, using eredis and poolboy.
pkg_eredis_pool_homepage = https://github.com/hiroeorz/eredis_pool
pkg_eredis_pool_fetch = git
pkg_eredis_pool_repo = https://github.com/hiroeorz/eredis_pool
pkg_eredis_pool_commit = master
PACKAGES += erlang_cep
pkg_erlang_cep_name = erlang_cep
pkg_erlang_cep_description = A basic CEP package written in erlang
pkg_erlang_cep_homepage = https://github.com/danmacklin/erlang_cep
pkg_erlang_cep_fetch = git
pkg_erlang_cep_repo = https://github.com/danmacklin/erlang_cep
pkg_erlang_cep_commit = master
PACKAGES += erlang_js
pkg_erlang_js_name = erlang_js
pkg_erlang_js_description = A linked-in driver for Erlang to Mozilla's Spidermonkey Javascript runtime.
pkg_erlang_js_homepage = https://github.com/basho/erlang_js
pkg_erlang_js_fetch = git
pkg_erlang_js_repo = https://github.com/basho/erlang_js
pkg_erlang_js_commit = master
PACKAGES += erlang_localtime
pkg_erlang_localtime_name = erlang_localtime
pkg_erlang_localtime_description = Erlang library for conversion from one local time to another
pkg_erlang_localtime_homepage = https://github.com/dmitryme/erlang_localtime
pkg_erlang_localtime_fetch = git
pkg_erlang_localtime_repo = https://github.com/dmitryme/erlang_localtime
pkg_erlang_localtime_commit = master
PACKAGES += erlang_smtp
pkg_erlang_smtp_name = erlang_smtp
pkg_erlang_smtp_description = Erlang SMTP and POP3 server code.
pkg_erlang_smtp_homepage = https://github.com/tonyg/erlang-smtp
pkg_erlang_smtp_fetch = git
pkg_erlang_smtp_repo = https://github.com/tonyg/erlang-smtp
pkg_erlang_smtp_commit = master
PACKAGES += erlang_term
pkg_erlang_term_name = erlang_term
pkg_erlang_term_description = Erlang Term Info
pkg_erlang_term_homepage = https://github.com/okeuday/erlang_term
pkg_erlang_term_fetch = git
pkg_erlang_term_repo = https://github.com/okeuday/erlang_term
pkg_erlang_term_commit = master
PACKAGES += erlastic_search
pkg_erlastic_search_name = erlastic_search
pkg_erlastic_search_description = An Erlang app for communicating with Elastic Search's rest interface.
pkg_erlastic_search_homepage = https://github.com/tsloughter/erlastic_search
pkg_erlastic_search_fetch = git
pkg_erlastic_search_repo = https://github.com/tsloughter/erlastic_search
pkg_erlastic_search_commit = master
PACKAGES += erlasticsearch
pkg_erlasticsearch_name = erlasticsearch
pkg_erlasticsearch_description = Erlang thrift interface to elastic_search
pkg_erlasticsearch_homepage = https://github.com/dieswaytoofast/erlasticsearch
pkg_erlasticsearch_fetch = git
pkg_erlasticsearch_repo = https://github.com/dieswaytoofast/erlasticsearch
pkg_erlasticsearch_commit = master
PACKAGES += erlbrake
pkg_erlbrake_name = erlbrake
pkg_erlbrake_description = Erlang Airbrake notification client
pkg_erlbrake_homepage = https://github.com/kenpratt/erlbrake
pkg_erlbrake_fetch = git
pkg_erlbrake_repo = https://github.com/kenpratt/erlbrake
pkg_erlbrake_commit = master
PACKAGES += erlcloud
pkg_erlcloud_name = erlcloud
pkg_erlcloud_description = Cloud Computing library for erlang (Amazon EC2, S3, SQS, SimpleDB, Mechanical Turk, ELB)
pkg_erlcloud_homepage = https://github.com/gleber/erlcloud
pkg_erlcloud_fetch = git
pkg_erlcloud_repo = https://github.com/gleber/erlcloud
pkg_erlcloud_commit = master
PACKAGES += erlcron
pkg_erlcron_name = erlcron
pkg_erlcron_description = Erlang cronish system
pkg_erlcron_homepage = https://github.com/erlware/erlcron
pkg_erlcron_fetch = git
pkg_erlcron_repo = https://github.com/erlware/erlcron
pkg_erlcron_commit = master
PACKAGES += erldb
pkg_erldb_name = erldb
pkg_erldb_description = ORM (Object-relational mapping) application implemented in Erlang
pkg_erldb_homepage = http://erldb.org
pkg_erldb_fetch = git
pkg_erldb_repo = https://github.com/erldb/erldb
pkg_erldb_commit = master
PACKAGES += erldis
pkg_erldis_name = erldis
pkg_erldis_description = redis erlang client library
pkg_erldis_homepage = https://github.com/cstar/erldis
pkg_erldis_fetch = git
pkg_erldis_repo = https://github.com/cstar/erldis
pkg_erldis_commit = master
PACKAGES += erldns
pkg_erldns_name = erldns
pkg_erldns_description = DNS server, in erlang.
pkg_erldns_homepage = https://github.com/aetrion/erl-dns
pkg_erldns_fetch = git
pkg_erldns_repo = https://github.com/aetrion/erl-dns
pkg_erldns_commit = master
PACKAGES += erldocker
pkg_erldocker_name = erldocker
pkg_erldocker_description = Docker Remote API client for Erlang
pkg_erldocker_homepage = https://github.com/proger/erldocker
pkg_erldocker_fetch = git
pkg_erldocker_repo = https://github.com/proger/erldocker
pkg_erldocker_commit = master
PACKAGES += erlfsmon
pkg_erlfsmon_name = erlfsmon
pkg_erlfsmon_description = Erlang filesystem event watcher for Linux and OSX
pkg_erlfsmon_homepage = https://github.com/proger/erlfsmon
pkg_erlfsmon_fetch = git
pkg_erlfsmon_repo = https://github.com/proger/erlfsmon
pkg_erlfsmon_commit = master
PACKAGES += erlgit
pkg_erlgit_name = erlgit
pkg_erlgit_description = Erlang convenience wrapper around git executable
pkg_erlgit_homepage = https://github.com/gleber/erlgit
pkg_erlgit_fetch = git
pkg_erlgit_repo = https://github.com/gleber/erlgit
pkg_erlgit_commit = master
PACKAGES += erlguten
pkg_erlguten_name = erlguten
pkg_erlguten_description = ErlGuten is a system for high-quality typesetting, written purely in Erlang.
pkg_erlguten_homepage = https://github.com/richcarl/erlguten
pkg_erlguten_fetch = git
pkg_erlguten_repo = https://github.com/richcarl/erlguten
pkg_erlguten_commit = master
PACKAGES += erlmc
pkg_erlmc_name = erlmc
pkg_erlmc_description = Erlang memcached binary protocol client
pkg_erlmc_homepage = https://github.com/jkvor/erlmc
pkg_erlmc_fetch = git
pkg_erlmc_repo = https://github.com/jkvor/erlmc
pkg_erlmc_commit = master
PACKAGES += erlmongo
pkg_erlmongo_name = erlmongo
pkg_erlmongo_description = Record based Erlang driver for MongoDB with gridfs support
pkg_erlmongo_homepage = https://github.com/SergejJurecko/erlmongo
pkg_erlmongo_fetch = git
pkg_erlmongo_repo = https://github.com/SergejJurecko/erlmongo
pkg_erlmongo_commit = master
PACKAGES += erlog
pkg_erlog_name = erlog
pkg_erlog_description = Prolog interpreter in and for Erlang
pkg_erlog_homepage = https://github.com/rvirding/erlog
pkg_erlog_fetch = git
pkg_erlog_repo = https://github.com/rvirding/erlog
pkg_erlog_commit = master
PACKAGES += erlpass
pkg_erlpass_name = erlpass
pkg_erlpass_description = A library to handle password hashing and changing in a safe manner, independent from any kind of storage whatsoever.
pkg_erlpass_homepage = https://github.com/ferd/erlpass
pkg_erlpass_fetch = git
pkg_erlpass_repo = https://github.com/ferd/erlpass
pkg_erlpass_commit = master
PACKAGES += erlport
pkg_erlport_name = erlport
pkg_erlport_description = ErlPort - connect Erlang to other languages
pkg_erlport_homepage = https://github.com/hdima/erlport
pkg_erlport_fetch = git
pkg_erlport_repo = https://github.com/hdima/erlport
pkg_erlport_commit = master
PACKAGES += erlsha2
pkg_erlsha2_name = erlsha2
pkg_erlsha2_description = SHA-224, SHA-256, SHA-384, SHA-512 implemented in Erlang NIFs.
pkg_erlsha2_homepage = https://github.com/vinoski/erlsha2
pkg_erlsha2_fetch = git
pkg_erlsha2_repo = https://github.com/vinoski/erlsha2
pkg_erlsha2_commit = master
PACKAGES += erlsh
pkg_erlsh_name = erlsh
pkg_erlsh_description = Erlang shell tools
pkg_erlsh_homepage = https://github.com/proger/erlsh
pkg_erlsh_fetch = git
pkg_erlsh_repo = https://github.com/proger/erlsh
pkg_erlsh_commit = master
PACKAGES += erlsom
pkg_erlsom_name = erlsom
pkg_erlsom_description = XML parser for Erlang
pkg_erlsom_homepage = https://github.com/willemdj/erlsom
pkg_erlsom_fetch = git
pkg_erlsom_repo = https://github.com/willemdj/erlsom
pkg_erlsom_commit = master
PACKAGES += erl_streams
pkg_erl_streams_name = erl_streams
pkg_erl_streams_description = Streams in Erlang
pkg_erl_streams_homepage = https://github.com/epappas/erl_streams
pkg_erl_streams_fetch = git
pkg_erl_streams_repo = https://github.com/epappas/erl_streams
pkg_erl_streams_commit = master
PACKAGES += erlubi
pkg_erlubi_name = erlubi
pkg_erlubi_description = Ubigraph Erlang Client (and Process Visualizer)
pkg_erlubi_homepage = https://github.com/krestenkrab/erlubi
pkg_erlubi_fetch = git
pkg_erlubi_repo = https://github.com/krestenkrab/erlubi
pkg_erlubi_commit = master
PACKAGES += erlvolt
pkg_erlvolt_name = erlvolt
pkg_erlvolt_description = VoltDB Erlang Client Driver
pkg_erlvolt_homepage = https://github.com/VoltDB/voltdb-client-erlang
pkg_erlvolt_fetch = git
pkg_erlvolt_repo = https://github.com/VoltDB/voltdb-client-erlang
pkg_erlvolt_commit = master
PACKAGES += erlware_commons
pkg_erlware_commons_name = erlware_commons
pkg_erlware_commons_description = Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components.
pkg_erlware_commons_homepage = https://github.com/erlware/erlware_commons
pkg_erlware_commons_fetch = git
pkg_erlware_commons_repo = https://github.com/erlware/erlware_commons
pkg_erlware_commons_commit = master
PACKAGES += erlydtl
pkg_erlydtl_name = erlydtl
pkg_erlydtl_description = Django Template Language for Erlang.
pkg_erlydtl_homepage = https://github.com/erlydtl/erlydtl
pkg_erlydtl_fetch = git
pkg_erlydtl_repo = https://github.com/erlydtl/erlydtl
pkg_erlydtl_commit = master
PACKAGES += errd
pkg_errd_name = errd
pkg_errd_description = Erlang RRDTool library
pkg_errd_homepage = https://github.com/archaelus/errd
pkg_errd_fetch = git
pkg_errd_repo = https://github.com/archaelus/errd
pkg_errd_commit = master
PACKAGES += erserve
pkg_erserve_name = erserve
pkg_erserve_description = Erlang/Rserve communication interface
pkg_erserve_homepage = https://github.com/del/erserve
pkg_erserve_fetch = git
pkg_erserve_repo = https://github.com/del/erserve
pkg_erserve_commit = master
PACKAGES += erwa
pkg_erwa_name = erwa
pkg_erwa_description = A WAMP router and client written in Erlang.
pkg_erwa_homepage = https://github.com/bwegh/erwa
pkg_erwa_fetch = git
pkg_erwa_repo = https://github.com/bwegh/erwa
pkg_erwa_commit = 0.1.1
PACKAGES += espec
pkg_espec_name = espec
pkg_espec_description = ESpec: Behaviour driven development framework for Erlang
pkg_espec_homepage = https://github.com/lucaspiller/espec
pkg_espec_fetch = git
pkg_espec_repo = https://github.com/lucaspiller/espec
pkg_espec_commit = master
PACKAGES += estatsd
pkg_estatsd_name = estatsd
pkg_estatsd_description = Erlang stats aggregation app that periodically flushes data to graphite
pkg_estatsd_homepage = https://github.com/RJ/estatsd
pkg_estatsd_fetch = git
pkg_estatsd_repo = https://github.com/RJ/estatsd
pkg_estatsd_commit = master
PACKAGES += etap
pkg_etap_name = etap
pkg_etap_description = etap is a simple erlang testing library that provides TAP compliant output.
pkg_etap_homepage = https://github.com/ngerakines/etap
pkg_etap_fetch = git
pkg_etap_repo = https://github.com/ngerakines/etap
pkg_etap_commit = master
PACKAGES += etest_http
pkg_etest_http_name = etest_http
pkg_etest_http_description = etest Assertions around HTTP (client-side)
pkg_etest_http_homepage = https://github.com/wooga/etest_http
pkg_etest_http_fetch = git
pkg_etest_http_repo = https://github.com/wooga/etest_http
pkg_etest_http_commit = master
PACKAGES += etest
pkg_etest_name = etest
pkg_etest_description = A lightweight, convention over configuration test framework for Erlang
pkg_etest_homepage = https://github.com/wooga/etest
pkg_etest_fetch = git
pkg_etest_repo = https://github.com/wooga/etest
pkg_etest_commit = master
PACKAGES += etoml
pkg_etoml_name = etoml
pkg_etoml_description = TOML language erlang parser
pkg_etoml_homepage = https://github.com/kalta/etoml
pkg_etoml_fetch = git
pkg_etoml_repo = https://github.com/kalta/etoml
pkg_etoml_commit = master
PACKAGES += eunit_formatters
pkg_eunit_formatters_name = eunit_formatters
pkg_eunit_formatters_description = Because eunit's output sucks. Let's make it better.
pkg_eunit_formatters_homepage = https://github.com/seancribbs/eunit_formatters
pkg_eunit_formatters_fetch = git
pkg_eunit_formatters_repo = https://github.com/seancribbs/eunit_formatters
pkg_eunit_formatters_commit = master
PACKAGES += eunit
pkg_eunit_name = eunit
pkg_eunit_description = The EUnit lightweight unit testing framework for Erlang - this is the canonical development repository.
pkg_eunit_homepage = https://github.com/richcarl/eunit
pkg_eunit_fetch = git
pkg_eunit_repo = https://github.com/richcarl/eunit
pkg_eunit_commit = master
PACKAGES += euthanasia
pkg_euthanasia_name = euthanasia
pkg_euthanasia_description = Merciful killer for your Erlang processes
pkg_euthanasia_homepage = https://github.com/doubleyou/euthanasia
pkg_euthanasia_fetch = git
pkg_euthanasia_repo = https://github.com/doubleyou/euthanasia
pkg_euthanasia_commit = master
PACKAGES += evum
pkg_evum_name = evum
pkg_evum_description = Spawn Linux VMs as Erlang processes in the Erlang VM
pkg_evum_homepage = https://github.com/msantos/evum
pkg_evum_fetch = git
pkg_evum_repo = https://github.com/msantos/evum
pkg_evum_commit = master
PACKAGES += exec
pkg_exec_name = exec
pkg_exec_description = Execute and control OS processes from Erlang/OTP.
pkg_exec_homepage = http://saleyn.github.com/erlexec
pkg_exec_fetch = git
pkg_exec_repo = https://github.com/saleyn/erlexec
pkg_exec_commit = master
PACKAGES += exml
pkg_exml_name = exml
pkg_exml_description = XML parsing library in Erlang
pkg_exml_homepage = https://github.com/paulgray/exml
pkg_exml_fetch = git
pkg_exml_repo = https://github.com/paulgray/exml
pkg_exml_commit = master
PACKAGES += exometer
pkg_exometer_name = exometer
pkg_exometer_description = Basic measurement objects and probe behavior
pkg_exometer_homepage = https://github.com/Feuerlabs/exometer
pkg_exometer_fetch = git
pkg_exometer_repo = https://github.com/Feuerlabs/exometer
pkg_exometer_commit = 1.2
PACKAGES += exs1024
pkg_exs1024_name = exs1024
pkg_exs1024_description = Xorshift1024star pseudo random number generator for Erlang.
pkg_exs1024_homepage = https://github.com/jj1bdx/exs1024
pkg_exs1024_fetch = git
pkg_exs1024_repo = https://github.com/jj1bdx/exs1024
pkg_exs1024_commit = master
PACKAGES += exs64
pkg_exs64_name = exs64
pkg_exs64_description = Xorshift64star pseudo random number generator for Erlang.
pkg_exs64_homepage = https://github.com/jj1bdx/exs64
pkg_exs64_fetch = git
pkg_exs64_repo = https://github.com/jj1bdx/exs64
pkg_exs64_commit = master
PACKAGES += exsplus116
pkg_exsplus116_name = exsplus116
pkg_exsplus116_description = Xorshift116plus for Erlang
pkg_exsplus116_homepage = https://github.com/jj1bdx/exsplus116
pkg_exsplus116_fetch = git
pkg_exsplus116_repo = https://github.com/jj1bdx/exsplus116
pkg_exsplus116_commit = master
PACKAGES += exsplus128
pkg_exsplus128_name = exsplus128
pkg_exsplus128_description = Xorshift128plus pseudo random number generator for Erlang.
pkg_exsplus128_homepage = https://github.com/jj1bdx/exsplus128
pkg_exsplus128_fetch = git
pkg_exsplus128_repo = https://github.com/jj1bdx/exsplus128
pkg_exsplus128_commit = master
PACKAGES += ezmq
pkg_ezmq_name = ezmq
pkg_ezmq_description = zMQ implemented in Erlang
pkg_ezmq_homepage = https://github.com/RoadRunnr/ezmq
pkg_ezmq_fetch = git
pkg_ezmq_repo = https://github.com/RoadRunnr/ezmq
pkg_ezmq_commit = master
PACKAGES += ezmtp
pkg_ezmtp_name = ezmtp
pkg_ezmtp_description = ZMTP protocol in pure Erlang.
pkg_ezmtp_homepage = https://github.com/a13x/ezmtp
pkg_ezmtp_fetch = git
pkg_ezmtp_repo = https://github.com/a13x/ezmtp
pkg_ezmtp_commit = master
PACKAGES += fast_disk_log
pkg_fast_disk_log_name = fast_disk_log
pkg_fast_disk_log_description = Pool-based asynchronous Erlang disk logger
pkg_fast_disk_log_homepage = https://github.com/lpgauth/fast_disk_log
pkg_fast_disk_log_fetch = git
pkg_fast_disk_log_repo = https://github.com/lpgauth/fast_disk_log
pkg_fast_disk_log_commit = master
PACKAGES += feeder
pkg_feeder_name = feeder
pkg_feeder_description = Stream parse RSS and Atom formatted XML feeds.
pkg_feeder_homepage = https://github.com/michaelnisi/feeder
pkg_feeder_fetch = git
pkg_feeder_repo = https://github.com/michaelnisi/feeder
pkg_feeder_commit = v1.4.6
PACKAGES += fix
pkg_fix_name = fix
pkg_fix_description = http://fixprotocol.org/ implementation.
pkg_fix_homepage = https://github.com/maxlapshin/fix
pkg_fix_fetch = git
pkg_fix_repo = https://github.com/maxlapshin/fix
pkg_fix_commit = master
PACKAGES += flower
pkg_flower_name = flower
pkg_flower_description = FlowER - a Erlang OpenFlow development platform
pkg_flower_homepage = https://github.com/travelping/flower
pkg_flower_fetch = git
pkg_flower_repo = https://github.com/travelping/flower
pkg_flower_commit = master
PACKAGES += fn
pkg_fn_name = fn
pkg_fn_description = Function utilities for Erlang
pkg_fn_homepage = https://github.com/reiddraper/fn
pkg_fn_fetch = git
pkg_fn_repo = https://github.com/reiddraper/fn
pkg_fn_commit = master
PACKAGES += folsom_cowboy
pkg_folsom_cowboy_name = folsom_cowboy
pkg_folsom_cowboy_description = A Cowboy based Folsom HTTP Wrapper.
pkg_folsom_cowboy_homepage = https://github.com/boundary/folsom_cowboy
pkg_folsom_cowboy_fetch = git
pkg_folsom_cowboy_repo = https://github.com/boundary/folsom_cowboy
pkg_folsom_cowboy_commit = master
PACKAGES += folsomite
pkg_folsomite_name = folsomite
pkg_folsomite_description = blow up your graphite / riemann server with folsom metrics
pkg_folsomite_homepage = https://github.com/campanja/folsomite
pkg_folsomite_fetch = git
pkg_folsomite_repo = https://github.com/campanja/folsomite
pkg_folsomite_commit = master
PACKAGES += folsom
pkg_folsom_name = folsom
pkg_folsom_description = Expose Erlang Events and Metrics
pkg_folsom_homepage = https://github.com/boundary/folsom
pkg_folsom_fetch = git
pkg_folsom_repo = https://github.com/boundary/folsom
pkg_folsom_commit = master
PACKAGES += fs
pkg_fs_name = fs
pkg_fs_description = Erlang FileSystem Listener
pkg_fs_homepage = https://github.com/synrc/fs
pkg_fs_fetch = git
pkg_fs_repo = https://github.com/synrc/fs
pkg_fs_commit = master
PACKAGES += fuse
pkg_fuse_name = fuse
pkg_fuse_description = A Circuit Breaker for Erlang
pkg_fuse_homepage = https://github.com/jlouis/fuse
pkg_fuse_fetch = git
pkg_fuse_repo = https://github.com/jlouis/fuse
pkg_fuse_commit = master
PACKAGES += gcm
pkg_gcm_name = gcm
pkg_gcm_description = An Erlang application for Google Cloud Messaging
pkg_gcm_homepage = https://github.com/pdincau/gcm-erlang
pkg_gcm_fetch = git
pkg_gcm_repo = https://github.com/pdincau/gcm-erlang
pkg_gcm_commit = master
PACKAGES += gcprof
pkg_gcprof_name = gcprof
pkg_gcprof_description = Garbage Collection profiler for Erlang
pkg_gcprof_homepage = https://github.com/knutin/gcprof
pkg_gcprof_fetch = git
pkg_gcprof_repo = https://github.com/knutin/gcprof
pkg_gcprof_commit = master
PACKAGES += geas
pkg_geas_name = geas
pkg_geas_description = Guess Erlang Application Scattering
pkg_geas_homepage = https://github.com/crownedgrouse/geas
pkg_geas_fetch = git
pkg_geas_repo = https://github.com/crownedgrouse/geas
pkg_geas_commit = master
PACKAGES += geef
pkg_geef_name = geef
pkg_geef_description = Git NEEEEF (Erlang NIF)
pkg_geef_homepage = https://github.com/carlosmn/geef
pkg_geef_fetch = git
pkg_geef_repo = https://github.com/carlosmn/geef
pkg_geef_commit = master
PACKAGES += gen_cycle
pkg_gen_cycle_name = gen_cycle
pkg_gen_cycle_description = Simple, generic OTP behaviour for recurring tasks
pkg_gen_cycle_homepage = https://github.com/aerosol/gen_cycle
pkg_gen_cycle_fetch = git
pkg_gen_cycle_repo = https://github.com/aerosol/gen_cycle
pkg_gen_cycle_commit = develop
PACKAGES += gen_icmp
pkg_gen_icmp_name = gen_icmp
pkg_gen_icmp_description = Erlang interface to ICMP sockets
pkg_gen_icmp_homepage = https://github.com/msantos/gen_icmp
pkg_gen_icmp_fetch = git
pkg_gen_icmp_repo = https://github.com/msantos/gen_icmp
pkg_gen_icmp_commit = master
PACKAGES += gen_nb_server
pkg_gen_nb_server_name = gen_nb_server
pkg_gen_nb_server_description = OTP behavior for writing non-blocking servers
pkg_gen_nb_server_homepage = https://github.com/kevsmith/gen_nb_server
pkg_gen_nb_server_fetch = git
pkg_gen_nb_server_repo = https://github.com/kevsmith/gen_nb_server
pkg_gen_nb_server_commit = master
PACKAGES += gen_paxos
pkg_gen_paxos_name = gen_paxos
pkg_gen_paxos_description = An Erlang/OTP-style implementation of the PAXOS distributed consensus protocol
pkg_gen_paxos_homepage = https://github.com/gburd/gen_paxos
pkg_gen_paxos_fetch = git
pkg_gen_paxos_repo = https://github.com/gburd/gen_paxos
pkg_gen_paxos_commit = master
PACKAGES += gen_smtp
pkg_gen_smtp_name = gen_smtp
pkg_gen_smtp_description = A generic Erlang SMTP server and client that can be extended via callback modules
pkg_gen_smtp_homepage = https://github.com/Vagabond/gen_smtp
pkg_gen_smtp_fetch = git
pkg_gen_smtp_repo = https://github.com/Vagabond/gen_smtp
pkg_gen_smtp_commit = master
PACKAGES += gen_tracker
pkg_gen_tracker_name = gen_tracker
pkg_gen_tracker_description = supervisor with ets handling of children and their metadata
pkg_gen_tracker_homepage = https://github.com/erlyvideo/gen_tracker
pkg_gen_tracker_fetch = git
pkg_gen_tracker_repo = https://github.com/erlyvideo/gen_tracker
pkg_gen_tracker_commit = master
PACKAGES += gen_unix
pkg_gen_unix_name = gen_unix
pkg_gen_unix_description = Erlang Unix socket interface
pkg_gen_unix_homepage = https://github.com/msantos/gen_unix
pkg_gen_unix_fetch = git
pkg_gen_unix_repo = https://github.com/msantos/gen_unix
pkg_gen_unix_commit = master
PACKAGES += getopt
pkg_getopt_name = getopt
pkg_getopt_description = Module to parse command line arguments using the GNU getopt syntax
pkg_getopt_homepage = https://github.com/jcomellas/getopt
pkg_getopt_fetch = git
pkg_getopt_repo = https://github.com/jcomellas/getopt
pkg_getopt_commit = master
PACKAGES += gettext
pkg_gettext_name = gettext
pkg_gettext_description = Erlang internationalization library.
pkg_gettext_homepage = https://github.com/etnt/gettext
pkg_gettext_fetch = git
pkg_gettext_repo = https://github.com/etnt/gettext
pkg_gettext_commit = master
PACKAGES += giallo
pkg_giallo_name = giallo
pkg_giallo_description = Small and flexible web framework on top of Cowboy
pkg_giallo_homepage = https://github.com/kivra/giallo
pkg_giallo_fetch = git
pkg_giallo_repo = https://github.com/kivra/giallo
pkg_giallo_commit = master
PACKAGES += gin
pkg_gin_name = gin
pkg_gin_description = The guards and for Erlang parse_transform
pkg_gin_homepage = https://github.com/mad-cocktail/gin
pkg_gin_fetch = git
pkg_gin_repo = https://github.com/mad-cocktail/gin
pkg_gin_commit = master
PACKAGES += gitty
pkg_gitty_name = gitty
pkg_gitty_description = Git access in erlang
pkg_gitty_homepage = https://github.com/maxlapshin/gitty
pkg_gitty_fetch = git
pkg_gitty_repo = https://github.com/maxlapshin/gitty
pkg_gitty_commit = master
PACKAGES += gold_fever
pkg_gold_fever_name = gold_fever
pkg_gold_fever_description = A Treasure Hunt for Erlangers
pkg_gold_fever_homepage = https://github.com/inaka/gold_fever
pkg_gold_fever_fetch = git
pkg_gold_fever_repo = https://github.com/inaka/gold_fever
pkg_gold_fever_commit = master
PACKAGES += gossiperl
pkg_gossiperl_name = gossiperl
pkg_gossiperl_description = Gossip middleware in Erlang
pkg_gossiperl_homepage = http://gossiperl.com/
pkg_gossiperl_fetch = git
pkg_gossiperl_repo = https://github.com/gossiperl/gossiperl
pkg_gossiperl_commit = master
PACKAGES += gpb
pkg_gpb_name = gpb
pkg_gpb_description = A Google Protobuf implementation for Erlang
pkg_gpb_homepage = https://github.com/tomas-abrahamsson/gpb
pkg_gpb_fetch = git
pkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb
pkg_gpb_commit = master
PACKAGES += gproc
pkg_gproc_name = gproc
pkg_gproc_description = Extended process registry for Erlang
pkg_gproc_homepage = https://github.com/uwiger/gproc
pkg_gproc_fetch = git
pkg_gproc_repo = https://github.com/uwiger/gproc
pkg_gproc_commit = master
PACKAGES += grapherl
pkg_grapherl_name = grapherl
pkg_grapherl_description = Create graphs of Erlang systems and programs
pkg_grapherl_homepage = https://github.com/eproxus/grapherl
pkg_grapherl_fetch = git
pkg_grapherl_repo = https://github.com/eproxus/grapherl
pkg_grapherl_commit = master
PACKAGES += gun
pkg_gun_name = gun
pkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang.
pkg_gun_homepage = http//ninenines.eu
pkg_gun_fetch = git
pkg_gun_repo = https://github.com/ninenines/gun
pkg_gun_commit = master
PACKAGES += gut
pkg_gut_name = gut
pkg_gut_description = gut is a template printing, aka scaffolding, tool for Erlang. Like rails generate or yeoman
pkg_gut_homepage = https://github.com/unbalancedparentheses/gut
pkg_gut_fetch = git
pkg_gut_repo = https://github.com/unbalancedparentheses/gut
pkg_gut_commit = master
PACKAGES += hackney
pkg_hackney_name = hackney
pkg_hackney_description = simple HTTP client in Erlang
pkg_hackney_homepage = https://github.com/benoitc/hackney
pkg_hackney_fetch = git
pkg_hackney_repo = https://github.com/benoitc/hackney
pkg_hackney_commit = master
PACKAGES += hamcrest
pkg_hamcrest_name = hamcrest
pkg_hamcrest_description = Erlang port of Hamcrest
pkg_hamcrest_homepage = https://github.com/hyperthunk/hamcrest-erlang
pkg_hamcrest_fetch = git
pkg_hamcrest_repo = https://github.com/hyperthunk/hamcrest-erlang
pkg_hamcrest_commit = master
PACKAGES += hanoidb
pkg_hanoidb_name = hanoidb
pkg_hanoidb_description = Erlang LSM BTree Storage
pkg_hanoidb_homepage = https://github.com/krestenkrab/hanoidb
pkg_hanoidb_fetch = git
pkg_hanoidb_repo = https://github.com/krestenkrab/hanoidb
pkg_hanoidb_commit = master
PACKAGES += hottub
pkg_hottub_name = hottub
pkg_hottub_description = Permanent Erlang Worker Pool
pkg_hottub_homepage = https://github.com/bfrog/hottub
pkg_hottub_fetch = git
pkg_hottub_repo = https://github.com/bfrog/hottub
pkg_hottub_commit = master
PACKAGES += hpack
pkg_hpack_name = hpack
pkg_hpack_description = HPACK Implementation for Erlang
pkg_hpack_homepage = https://github.com/joedevivo/hpack
pkg_hpack_fetch = git
pkg_hpack_repo = https://github.com/joedevivo/hpack
pkg_hpack_commit = master
PACKAGES += hyper
pkg_hyper_name = hyper
pkg_hyper_description = Erlang implementation of HyperLogLog
pkg_hyper_homepage = https://github.com/GameAnalytics/hyper
pkg_hyper_fetch = git
pkg_hyper_repo = https://github.com/GameAnalytics/hyper
pkg_hyper_commit = master
PACKAGES += ibrowse
pkg_ibrowse_name = ibrowse
pkg_ibrowse_description = Erlang HTTP client
pkg_ibrowse_homepage = https://github.com/cmullaparthi/ibrowse
pkg_ibrowse_fetch = git
pkg_ibrowse_repo = https://github.com/cmullaparthi/ibrowse
pkg_ibrowse_commit = v4.1.1
PACKAGES += ierlang
pkg_ierlang_name = ierlang
pkg_ierlang_description = An Erlang language kernel for IPython.
pkg_ierlang_homepage = https://github.com/robbielynch/ierlang
pkg_ierlang_fetch = git
pkg_ierlang_repo = https://github.com/robbielynch/ierlang
pkg_ierlang_commit = master
PACKAGES += iota
pkg_iota_name = iota
pkg_iota_description = iota (Inter-dependency Objective Testing Apparatus) - a tool to enforce clean separation of responsibilities in Erlang code
pkg_iota_homepage = https://github.com/jpgneves/iota
pkg_iota_fetch = git
pkg_iota_repo = https://github.com/jpgneves/iota
pkg_iota_commit = master
PACKAGES += ircd
pkg_ircd_name = ircd
pkg_ircd_description = A pluggable IRC daemon application/library for Erlang.
pkg_ircd_homepage = https://github.com/tonyg/erlang-ircd
pkg_ircd_fetch = git
pkg_ircd_repo = https://github.com/tonyg/erlang-ircd
pkg_ircd_commit = master
PACKAGES += irc_lib
pkg_irc_lib_name = irc_lib
pkg_irc_lib_description = Erlang irc client library
pkg_irc_lib_homepage = https://github.com/OtpChatBot/irc_lib
pkg_irc_lib_fetch = git
pkg_irc_lib_repo = https://github.com/OtpChatBot/irc_lib
pkg_irc_lib_commit = master
PACKAGES += iris
pkg_iris_name = iris
pkg_iris_description = Iris Erlang binding
pkg_iris_homepage = https://github.com/project-iris/iris-erl
pkg_iris_fetch = git
pkg_iris_repo = https://github.com/project-iris/iris-erl
pkg_iris_commit = master
PACKAGES += iso8601
pkg_iso8601_name = iso8601
pkg_iso8601_description = Erlang ISO 8601 date formatter/parser
pkg_iso8601_homepage = https://github.com/seansawyer/erlang_iso8601
pkg_iso8601_fetch = git
pkg_iso8601_repo = https://github.com/seansawyer/erlang_iso8601
pkg_iso8601_commit = master
PACKAGES += itweet
pkg_itweet_name = itweet
pkg_itweet_description = Twitter Stream API on ibrowse
pkg_itweet_homepage = http://inaka.github.com/itweet/
pkg_itweet_fetch = git
pkg_itweet_repo = https://github.com/inaka/itweet
pkg_itweet_commit = v2.0
PACKAGES += jerg
pkg_jerg_name = jerg
pkg_jerg_description = JSON Schema to Erlang Records Generator
pkg_jerg_homepage = https://github.com/ddossot/jerg
pkg_jerg_fetch = git
pkg_jerg_repo = https://github.com/ddossot/jerg
pkg_jerg_commit = master
PACKAGES += jesse
pkg_jesse_name = jesse
pkg_jesse_description = jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang.
pkg_jesse_homepage = https://github.com/klarna/jesse
pkg_jesse_fetch = git
pkg_jesse_repo = https://github.com/klarna/jesse
pkg_jesse_commit = master
PACKAGES += jiffy
pkg_jiffy_name = jiffy
pkg_jiffy_description = JSON NIFs for Erlang.
pkg_jiffy_homepage = https://github.com/davisp/jiffy
pkg_jiffy_fetch = git
pkg_jiffy_repo = https://github.com/davisp/jiffy
pkg_jiffy_commit = master
PACKAGES += jiffy_v
pkg_jiffy_v_name = jiffy_v
pkg_jiffy_v_description = JSON validation utility
pkg_jiffy_v_homepage = https://github.com/shizzard/jiffy-v
pkg_jiffy_v_fetch = git
pkg_jiffy_v_repo = https://github.com/shizzard/jiffy-v
pkg_jiffy_v_commit = 0.3.3
PACKAGES += jobs
pkg_jobs_name = jobs
pkg_jobs_description = a Job scheduler for load regulation
pkg_jobs_homepage = https://github.com/esl/jobs
pkg_jobs_fetch = git
pkg_jobs_repo = https://github.com/esl/jobs
pkg_jobs_commit = 0.3
PACKAGES += joxa
pkg_joxa_name = joxa
pkg_joxa_description = A Modern Lisp for the Erlang VM
pkg_joxa_homepage = https://github.com/joxa/joxa
pkg_joxa_fetch = git
pkg_joxa_repo = https://github.com/joxa/joxa
pkg_joxa_commit = master
PACKAGES += jsonerl
pkg_jsonerl_name = jsonerl
pkg_jsonerl_description = yet another but slightly different erlang <-> json encoder/decoder
pkg_jsonerl_homepage = https://github.com/lambder/jsonerl
pkg_jsonerl_fetch = git
pkg_jsonerl_repo = https://github.com/lambder/jsonerl
pkg_jsonerl_commit = master
PACKAGES += json
pkg_json_name = json
pkg_json_description = a high level json library for erlang (17.0+)
pkg_json_homepage = https://github.com/talentdeficit/json
pkg_json_fetch = git
pkg_json_repo = https://github.com/talentdeficit/json
pkg_json_commit = master
PACKAGES += jsonpath
pkg_jsonpath_name = jsonpath
pkg_jsonpath_description = Fast Erlang JSON data retrieval and updates via javascript-like notation
pkg_jsonpath_homepage = https://github.com/GeneStevens/jsonpath
pkg_jsonpath_fetch = git
pkg_jsonpath_repo = https://github.com/GeneStevens/jsonpath
pkg_jsonpath_commit = master
PACKAGES += json_rec
pkg_json_rec_name = json_rec
pkg_json_rec_description = JSON to erlang record
pkg_json_rec_homepage = https://github.com/justinkirby/json_rec
pkg_json_rec_fetch = git
pkg_json_rec_repo = https://github.com/justinkirby/json_rec
pkg_json_rec_commit = master
PACKAGES += jsonx
pkg_jsonx_name = jsonx
pkg_jsonx_description = JSONX is an Erlang library for efficient decode and encode JSON, written in C.
pkg_jsonx_homepage = https://github.com/iskra/jsonx
pkg_jsonx_fetch = git
pkg_jsonx_repo = https://github.com/iskra/jsonx
pkg_jsonx_commit = master
PACKAGES += jsx
pkg_jsx_name = jsx
pkg_jsx_description = An Erlang application for consuming, producing and manipulating JSON.
pkg_jsx_homepage = https://github.com/talentdeficit/jsx
pkg_jsx_fetch = git
pkg_jsx_repo = https://github.com/talentdeficit/jsx
pkg_jsx_commit = master
PACKAGES += kafka
pkg_kafka_name = kafka
pkg_kafka_description = Kafka consumer and producer in Erlang
pkg_kafka_homepage = https://github.com/wooga/kafka-erlang
pkg_kafka_fetch = git
pkg_kafka_repo = https://github.com/wooga/kafka-erlang
pkg_kafka_commit = master
PACKAGES += kai
pkg_kai_name = kai
pkg_kai_description = DHT storage by Takeshi Inoue
pkg_kai_homepage = https://github.com/synrc/kai
pkg_kai_fetch = git
pkg_kai_repo = https://github.com/synrc/kai
pkg_kai_commit = master
PACKAGES += katja
pkg_katja_name = katja
pkg_katja_description = A simple Riemann client written in Erlang.
pkg_katja_homepage = https://github.com/nifoc/katja
pkg_katja_fetch = git
pkg_katja_repo = https://github.com/nifoc/katja
pkg_katja_commit = master
PACKAGES += kdht
pkg_kdht_name = kdht
pkg_kdht_description = kdht is an erlang DHT implementation
pkg_kdht_homepage = https://github.com/kevinlynx/kdht
pkg_kdht_fetch = git
pkg_kdht_repo = https://github.com/kevinlynx/kdht
pkg_kdht_commit = master
PACKAGES += key2value
pkg_key2value_name = key2value
pkg_key2value_description = Erlang 2-way map
pkg_key2value_homepage = https://github.com/okeuday/key2value
pkg_key2value_fetch = git
pkg_key2value_repo = https://github.com/okeuday/key2value
pkg_key2value_commit = master
PACKAGES += keys1value
pkg_keys1value_name = keys1value
pkg_keys1value_description = Erlang set associative map for key lists
pkg_keys1value_homepage = https://github.com/okeuday/keys1value
pkg_keys1value_fetch = git
pkg_keys1value_repo = https://github.com/okeuday/keys1value
pkg_keys1value_commit = master
PACKAGES += kinetic
pkg_kinetic_name = kinetic
pkg_kinetic_description = Erlang Kinesis Client
pkg_kinetic_homepage = https://github.com/AdRoll/kinetic
pkg_kinetic_fetch = git
pkg_kinetic_repo = https://github.com/AdRoll/kinetic
pkg_kinetic_commit = master
PACKAGES += kjell
pkg_kjell_name = kjell
pkg_kjell_description = Erlang Shell
pkg_kjell_homepage = https://github.com/karlll/kjell
pkg_kjell_fetch = git
pkg_kjell_repo = https://github.com/karlll/kjell
pkg_kjell_commit = master
PACKAGES += kraken
pkg_kraken_name = kraken
pkg_kraken_description = Distributed Pubsub Server for Realtime Apps
pkg_kraken_homepage = https://github.com/Asana/kraken
pkg_kraken_fetch = git
pkg_kraken_repo = https://github.com/Asana/kraken
pkg_kraken_commit = master
PACKAGES += kucumberl
pkg_kucumberl_name = kucumberl
pkg_kucumberl_description = A pure-erlang, open-source, implementation of Cucumber
pkg_kucumberl_homepage = https://github.com/openshine/kucumberl
pkg_kucumberl_fetch = git
pkg_kucumberl_repo = https://github.com/openshine/kucumberl
pkg_kucumberl_commit = master
PACKAGES += kvc
pkg_kvc_name = kvc
pkg_kvc_description = KVC - Key Value Coding for Erlang data structures
pkg_kvc_homepage = https://github.com/etrepum/kvc
pkg_kvc_fetch = git
pkg_kvc_repo = https://github.com/etrepum/kvc
pkg_kvc_commit = master
PACKAGES += kvlists
pkg_kvlists_name = kvlists
pkg_kvlists_description = Lists of key-value pairs (decoded JSON) in Erlang
pkg_kvlists_homepage = https://github.com/jcomellas/kvlists
pkg_kvlists_fetch = git
pkg_kvlists_repo = https://github.com/jcomellas/kvlists
pkg_kvlists_commit = master
PACKAGES += kvs
pkg_kvs_name = kvs
pkg_kvs_description = Container and Iterator
pkg_kvs_homepage = https://github.com/synrc/kvs
pkg_kvs_fetch = git
pkg_kvs_repo = https://github.com/synrc/kvs
pkg_kvs_commit = master
PACKAGES += lager_amqp_backend
pkg_lager_amqp_backend_name = lager_amqp_backend
pkg_lager_amqp_backend_description = AMQP RabbitMQ Lager backend
pkg_lager_amqp_backend_homepage = https://github.com/jbrisbin/lager_amqp_backend
pkg_lager_amqp_backend_fetch = git
pkg_lager_amqp_backend_repo = https://github.com/jbrisbin/lager_amqp_backend
pkg_lager_amqp_backend_commit = master
PACKAGES += lager
pkg_lager_name = lager
pkg_lager_description = A logging framework for Erlang/OTP.
pkg_lager_homepage = https://github.com/basho/lager
pkg_lager_fetch = git
pkg_lager_repo = https://github.com/basho/lager
pkg_lager_commit = master
PACKAGES += lager_syslog
pkg_lager_syslog_name = lager_syslog
pkg_lager_syslog_description = Syslog backend for lager
pkg_lager_syslog_homepage = https://github.com/basho/lager_syslog
pkg_lager_syslog_fetch = git
pkg_lager_syslog_repo = https://github.com/basho/lager_syslog
pkg_lager_syslog_commit = master
PACKAGES += lambdapad
pkg_lambdapad_name = lambdapad
pkg_lambdapad_description = Static site generator using Erlang. Yes, Erlang.
pkg_lambdapad_homepage = https://github.com/gar1t/lambdapad
pkg_lambdapad_fetch = git
pkg_lambdapad_repo = https://github.com/gar1t/lambdapad
pkg_lambdapad_commit = master
PACKAGES += lasp
pkg_lasp_name = lasp
pkg_lasp_description = A Language for Distributed, Eventually Consistent Computations
pkg_lasp_homepage = http://lasp-lang.org/
pkg_lasp_fetch = git
pkg_lasp_repo = https://github.com/lasp-lang/lasp
pkg_lasp_commit = master
PACKAGES += lasse
pkg_lasse_name = lasse
pkg_lasse_description = SSE handler for Cowboy
pkg_lasse_homepage = https://github.com/inaka/lasse
pkg_lasse_fetch = git
pkg_lasse_repo = https://github.com/inaka/lasse
pkg_lasse_commit = 0.1.0
PACKAGES += ldap
pkg_ldap_name = ldap
pkg_ldap_description = LDAP server written in Erlang
pkg_ldap_homepage = https://github.com/spawnproc/ldap
pkg_ldap_fetch = git
pkg_ldap_repo = https://github.com/spawnproc/ldap
pkg_ldap_commit = master
PACKAGES += lethink
pkg_lethink_name = lethink
pkg_lethink_description = erlang driver for rethinkdb
pkg_lethink_homepage = https://github.com/taybin/lethink
pkg_lethink_fetch = git
pkg_lethink_repo = https://github.com/taybin/lethink
pkg_lethink_commit = master
PACKAGES += lfe
pkg_lfe_name = lfe
pkg_lfe_description = Lisp Flavoured Erlang (LFE)
pkg_lfe_homepage = https://github.com/rvirding/lfe
pkg_lfe_fetch = git
pkg_lfe_repo = https://github.com/rvirding/lfe
pkg_lfe_commit = master
PACKAGES += ling
pkg_ling_name = ling
pkg_ling_description = Erlang on Xen
pkg_ling_homepage = https://github.com/cloudozer/ling
pkg_ling_fetch = git
pkg_ling_repo = https://github.com/cloudozer/ling
pkg_ling_commit = master
PACKAGES += live
pkg_live_name = live
pkg_live_description = Automated module and configuration reloader.
pkg_live_homepage = http://ninenines.eu
pkg_live_fetch = git
pkg_live_repo = https://github.com/ninenines/live
pkg_live_commit = master
PACKAGES += lmq
pkg_lmq_name = lmq
pkg_lmq_description = Lightweight Message Queue
pkg_lmq_homepage = https://github.com/iij/lmq
pkg_lmq_fetch = git
pkg_lmq_repo = https://github.com/iij/lmq
pkg_lmq_commit = master
PACKAGES += locker
pkg_locker_name = locker
pkg_locker_description = Atomic distributed 'check and set' for short-lived keys
pkg_locker_homepage = https://github.com/wooga/locker
pkg_locker_fetch = git
pkg_locker_repo = https://github.com/wooga/locker
pkg_locker_commit = master
PACKAGES += locks
pkg_locks_name = locks
pkg_locks_description = A scalable, deadlock-resolving resource locker
pkg_locks_homepage = https://github.com/uwiger/locks
pkg_locks_fetch = git
pkg_locks_repo = https://github.com/uwiger/locks
pkg_locks_commit = master
PACKAGES += log4erl
pkg_log4erl_name = log4erl
pkg_log4erl_description = A logger for erlang in the spirit of Log4J.
pkg_log4erl_homepage = https://github.com/ahmednawras/log4erl
pkg_log4erl_fetch = git
pkg_log4erl_repo = https://github.com/ahmednawras/log4erl
pkg_log4erl_commit = master
PACKAGES += lol
pkg_lol_name = lol
pkg_lol_description = Lisp on erLang, and programming is fun again
pkg_lol_homepage = https://github.com/b0oh/lol
pkg_lol_fetch = git
pkg_lol_repo = https://github.com/b0oh/lol
pkg_lol_commit = master
PACKAGES += lucid
pkg_lucid_name = lucid
pkg_lucid_description = HTTP/2 server written in Erlang
pkg_lucid_homepage = https://github.com/tatsuhiro-t/lucid
pkg_lucid_fetch = git
pkg_lucid_repo = https://github.com/tatsuhiro-t/lucid
pkg_lucid_commit = master
PACKAGES += luerl
pkg_luerl_name = luerl
pkg_luerl_description = Lua in Erlang
pkg_luerl_homepage = https://github.com/rvirding/luerl
pkg_luerl_fetch = git
pkg_luerl_repo = https://github.com/rvirding/luerl
pkg_luerl_commit = develop
PACKAGES += luwak
pkg_luwak_name = luwak
pkg_luwak_description = Large-object storage interface for Riak
pkg_luwak_homepage = https://github.com/basho/luwak
pkg_luwak_fetch = git
pkg_luwak_repo = https://github.com/basho/luwak
pkg_luwak_commit = master
PACKAGES += lux
pkg_lux_name = lux
pkg_lux_description = Lux (LUcid eXpect scripting) simplifies test automation and provides an Expect-style execution of commands
pkg_lux_homepage = https://github.com/hawk/lux
pkg_lux_fetch = git
pkg_lux_repo = https://github.com/hawk/lux
pkg_lux_commit = master
PACKAGES += machi
pkg_machi_name = machi
pkg_machi_description = Machi file store
pkg_machi_homepage = https://github.com/basho/machi
pkg_machi_fetch = git
pkg_machi_repo = https://github.com/basho/machi
pkg_machi_commit = master
PACKAGES += mad
pkg_mad_name = mad
pkg_mad_description = Small and Fast Rebar Replacement
pkg_mad_homepage = https://github.com/synrc/mad
pkg_mad_fetch = git
pkg_mad_repo = https://github.com/synrc/mad
pkg_mad_commit = master
PACKAGES += marina
pkg_marina_name = marina
pkg_marina_description = Non-blocking Erlang Cassandra CQL3 client
pkg_marina_homepage = https://github.com/lpgauth/marina
pkg_marina_fetch = git
pkg_marina_repo = https://github.com/lpgauth/marina
pkg_marina_commit = master
PACKAGES += mavg
pkg_mavg_name = mavg
pkg_mavg_description = Erlang :: Exponential moving average library
pkg_mavg_homepage = https://github.com/EchoTeam/mavg
pkg_mavg_fetch = git
pkg_mavg_repo = https://github.com/EchoTeam/mavg
pkg_mavg_commit = master
PACKAGES += mcd
pkg_mcd_name = mcd
pkg_mcd_description = Fast memcached protocol client in pure Erlang
pkg_mcd_homepage = https://github.com/EchoTeam/mcd
pkg_mcd_fetch = git
pkg_mcd_repo = https://github.com/EchoTeam/mcd
pkg_mcd_commit = master
PACKAGES += mcerlang
pkg_mcerlang_name = mcerlang
pkg_mcerlang_description = The McErlang model checker for Erlang
pkg_mcerlang_homepage = https://github.com/fredlund/McErlang
pkg_mcerlang_fetch = git
pkg_mcerlang_repo = https://github.com/fredlund/McErlang
pkg_mcerlang_commit = master
PACKAGES += mc_erl
pkg_mc_erl_name = mc_erl
pkg_mc_erl_description = mc-erl is a server for Minecraft 1.4.7 written in Erlang.
pkg_mc_erl_homepage = https://github.com/clonejo/mc-erl
pkg_mc_erl_fetch = git
pkg_mc_erl_repo = https://github.com/clonejo/mc-erl
pkg_mc_erl_commit = master
PACKAGES += meck
pkg_meck_name = meck
pkg_meck_description = A mocking library for Erlang
pkg_meck_homepage = https://github.com/eproxus/meck
pkg_meck_fetch = git
pkg_meck_repo = https://github.com/eproxus/meck
pkg_meck_commit = master
PACKAGES += mekao
pkg_mekao_name = mekao
pkg_mekao_description = SQL constructor
pkg_mekao_homepage = https://github.com/ddosia/mekao
pkg_mekao_fetch = git
pkg_mekao_repo = https://github.com/ddosia/mekao
pkg_mekao_commit = master
PACKAGES += memo
pkg_memo_name = memo
pkg_memo_description = Erlang memoization server
pkg_memo_homepage = https://github.com/tuncer/memo
pkg_memo_fetch = git
pkg_memo_repo = https://github.com/tuncer/memo
pkg_memo_commit = master
PACKAGES += merge_index
pkg_merge_index_name = merge_index
pkg_merge_index_description = MergeIndex is an Erlang library for storing ordered sets on disk. It is very similar to an SSTable (in Google's Bigtable) or an HFile (in Hadoop).
pkg_merge_index_homepage = https://github.com/basho/merge_index
pkg_merge_index_fetch = git
pkg_merge_index_repo = https://github.com/basho/merge_index
pkg_merge_index_commit = master
PACKAGES += merl
pkg_merl_name = merl
pkg_merl_description = Metaprogramming in Erlang
pkg_merl_homepage = https://github.com/richcarl/merl
pkg_merl_fetch = git
pkg_merl_repo = https://github.com/richcarl/merl
pkg_merl_commit = master
PACKAGES += mimetypes
pkg_mimetypes_name = mimetypes
pkg_mimetypes_description = Erlang MIME types library
pkg_mimetypes_homepage = https://github.com/spawngrid/mimetypes
pkg_mimetypes_fetch = git
pkg_mimetypes_repo = https://github.com/spawngrid/mimetypes
pkg_mimetypes_commit = master
PACKAGES += mixer
pkg_mixer_name = mixer
pkg_mixer_description = Mix in functions from other modules
pkg_mixer_homepage = https://github.com/chef/mixer
pkg_mixer_fetch = git
pkg_mixer_repo = https://github.com/chef/mixer
pkg_mixer_commit = master
PACKAGES += mochiweb
pkg_mochiweb_name = mochiweb
pkg_mochiweb_description = MochiWeb is an Erlang library for building lightweight HTTP servers.
pkg_mochiweb_homepage = https://github.com/mochi/mochiweb
pkg_mochiweb_fetch = git
pkg_mochiweb_repo = https://github.com/mochi/mochiweb
pkg_mochiweb_commit = master
PACKAGES += mochiweb_xpath
pkg_mochiweb_xpath_name = mochiweb_xpath
pkg_mochiweb_xpath_description = XPath support for mochiweb's html parser
pkg_mochiweb_xpath_homepage = https://github.com/retnuh/mochiweb_xpath
pkg_mochiweb_xpath_fetch = git
pkg_mochiweb_xpath_repo = https://github.com/retnuh/mochiweb_xpath
pkg_mochiweb_xpath_commit = master
PACKAGES += mockgyver
pkg_mockgyver_name = mockgyver
pkg_mockgyver_description = A mocking library for Erlang
pkg_mockgyver_homepage = https://github.com/klajo/mockgyver
pkg_mockgyver_fetch = git
pkg_mockgyver_repo = https://github.com/klajo/mockgyver
pkg_mockgyver_commit = master
PACKAGES += modlib
pkg_modlib_name = modlib
pkg_modlib_description = Web framework based on Erlang's inets httpd
pkg_modlib_homepage = https://github.com/gar1t/modlib
pkg_modlib_fetch = git
pkg_modlib_repo = https://github.com/gar1t/modlib
pkg_modlib_commit = master
PACKAGES += mongodb
pkg_mongodb_name = mongodb
pkg_mongodb_description = MongoDB driver for Erlang
pkg_mongodb_homepage = https://github.com/comtihon/mongodb-erlang
pkg_mongodb_fetch = git
pkg_mongodb_repo = https://github.com/comtihon/mongodb-erlang
pkg_mongodb_commit = master
PACKAGES += mongooseim
pkg_mongooseim_name = mongooseim
pkg_mongooseim_description = Jabber / XMPP server with focus on performance and scalability, by Erlang Solutions
pkg_mongooseim_homepage = https://www.erlang-solutions.com/products/mongooseim-massively-scalable-ejabberd-platform
pkg_mongooseim_fetch = git
pkg_mongooseim_repo = https://github.com/esl/MongooseIM
pkg_mongooseim_commit = master
PACKAGES += moyo
pkg_moyo_name = moyo
pkg_moyo_description = Erlang utility functions library
pkg_moyo_homepage = https://github.com/dwango/moyo
pkg_moyo_fetch = git
pkg_moyo_repo = https://github.com/dwango/moyo
pkg_moyo_commit = master
PACKAGES += msgpack
pkg_msgpack_name = msgpack
pkg_msgpack_description = MessagePack (de)serializer implementation for Erlang
pkg_msgpack_homepage = https://github.com/msgpack/msgpack-erlang
pkg_msgpack_fetch = git
pkg_msgpack_repo = https://github.com/msgpack/msgpack-erlang
pkg_msgpack_commit = master
PACKAGES += mu2
pkg_mu2_name = mu2
pkg_mu2_description = Erlang mutation testing tool
pkg_mu2_homepage = https://github.com/ramsay-t/mu2
pkg_mu2_fetch = git
pkg_mu2_repo = https://github.com/ramsay-t/mu2
pkg_mu2_commit = master
PACKAGES += mustache
pkg_mustache_name = mustache
pkg_mustache_description = Mustache template engine for Erlang.
pkg_mustache_homepage = https://github.com/mojombo/mustache.erl
pkg_mustache_fetch = git
pkg_mustache_repo = https://github.com/mojombo/mustache.erl
pkg_mustache_commit = master
PACKAGES += myproto
pkg_myproto_name = myproto
pkg_myproto_description = MySQL Server Protocol in Erlang
pkg_myproto_homepage = https://github.com/altenwald/myproto
pkg_myproto_fetch = git
pkg_myproto_repo = https://github.com/altenwald/myproto
pkg_myproto_commit = master
PACKAGES += mysql
pkg_mysql_name = mysql
pkg_mysql_description = Erlang MySQL Driver (from code.google.com)
pkg_mysql_homepage = https://github.com/dizzyd/erlang-mysql-driver
pkg_mysql_fetch = git
pkg_mysql_repo = https://github.com/dizzyd/erlang-mysql-driver
pkg_mysql_commit = master
PACKAGES += n2o
pkg_n2o_name = n2o
pkg_n2o_description = WebSocket Application Server
pkg_n2o_homepage = https://github.com/5HT/n2o
pkg_n2o_fetch = git
pkg_n2o_repo = https://github.com/5HT/n2o
pkg_n2o_commit = master
PACKAGES += nat_upnp
pkg_nat_upnp_name = nat_upnp
pkg_nat_upnp_description = Erlang library to map your internal port to an external using UNP IGD
pkg_nat_upnp_homepage = https://github.com/benoitc/nat_upnp
pkg_nat_upnp_fetch = git
pkg_nat_upnp_repo = https://github.com/benoitc/nat_upnp
pkg_nat_upnp_commit = master
PACKAGES += neo4j
pkg_neo4j_name = neo4j
pkg_neo4j_description = Erlang client library for Neo4J.
pkg_neo4j_homepage = https://github.com/dmitriid/neo4j-erlang
pkg_neo4j_fetch = git
pkg_neo4j_repo = https://github.com/dmitriid/neo4j-erlang
pkg_neo4j_commit = master
PACKAGES += neotoma
pkg_neotoma_name = neotoma
pkg_neotoma_description = Erlang library and packrat parser-generator for parsing expression grammars.
pkg_neotoma_homepage = https://github.com/seancribbs/neotoma
pkg_neotoma_fetch = git
pkg_neotoma_repo = https://github.com/seancribbs/neotoma
pkg_neotoma_commit = master
PACKAGES += newrelic
pkg_newrelic_name = newrelic
pkg_newrelic_description = Erlang library for sending metrics to New Relic
pkg_newrelic_homepage = https://github.com/wooga/newrelic-erlang
pkg_newrelic_fetch = git
pkg_newrelic_repo = https://github.com/wooga/newrelic-erlang
pkg_newrelic_commit = master
PACKAGES += nifty
pkg_nifty_name = nifty
pkg_nifty_description = Erlang NIF wrapper generator
pkg_nifty_homepage = https://github.com/parapluu/nifty
pkg_nifty_fetch = git
pkg_nifty_repo = https://github.com/parapluu/nifty
pkg_nifty_commit = master
PACKAGES += nitrogen_core
pkg_nitrogen_core_name = nitrogen_core
pkg_nitrogen_core_description = The core Nitrogen library.
pkg_nitrogen_core_homepage = http://nitrogenproject.com/
pkg_nitrogen_core_fetch = git
pkg_nitrogen_core_repo = https://github.com/nitrogen/nitrogen_core
pkg_nitrogen_core_commit = master
PACKAGES += nkbase
pkg_nkbase_name = nkbase
pkg_nkbase_description = NkBASE distributed database
pkg_nkbase_homepage = https://github.com/Nekso/nkbase
pkg_nkbase_fetch = git
pkg_nkbase_repo = https://github.com/Nekso/nkbase
pkg_nkbase_commit = develop
PACKAGES += nkdocker
pkg_nkdocker_name = nkdocker
pkg_nkdocker_description = Erlang Docker client
pkg_nkdocker_homepage = https://github.com/Nekso/nkdocker
pkg_nkdocker_fetch = git
pkg_nkdocker_repo = https://github.com/Nekso/nkdocker
pkg_nkdocker_commit = master
PACKAGES += nkpacket
pkg_nkpacket_name = nkpacket
pkg_nkpacket_description = Generic Erlang transport layer
pkg_nkpacket_homepage = https://github.com/Nekso/nkpacket
pkg_nkpacket_fetch = git
pkg_nkpacket_repo = https://github.com/Nekso/nkpacket
pkg_nkpacket_commit = master
PACKAGES += nodefinder
pkg_nodefinder_name = nodefinder
pkg_nodefinder_description = automatic node discovery via UDP multicast
pkg_nodefinder_homepage = https://github.com/erlanger/nodefinder
pkg_nodefinder_fetch = git
pkg_nodefinder_repo = https://github.com/okeuday/nodefinder
pkg_nodefinder_commit = master
PACKAGES += nprocreg
pkg_nprocreg_name = nprocreg
pkg_nprocreg_description = Minimal Distributed Erlang Process Registry
pkg_nprocreg_homepage = http://nitrogenproject.com/
pkg_nprocreg_fetch = git
pkg_nprocreg_repo = https://github.com/nitrogen/nprocreg
pkg_nprocreg_commit = master
PACKAGES += oauth2c
pkg_oauth2c_name = oauth2c
pkg_oauth2c_description = Erlang OAuth2 Client
pkg_oauth2c_homepage = https://github.com/kivra/oauth2_client
pkg_oauth2c_fetch = git
pkg_oauth2c_repo = https://github.com/kivra/oauth2_client
pkg_oauth2c_commit = master
PACKAGES += oauth2
pkg_oauth2_name = oauth2
pkg_oauth2_description = Erlang Oauth2 implementation
pkg_oauth2_homepage = https://github.com/kivra/oauth2
pkg_oauth2_fetch = git
pkg_oauth2_repo = https://github.com/kivra/oauth2
pkg_oauth2_commit = master
PACKAGES += oauth
pkg_oauth_name = oauth
pkg_oauth_description = An Erlang OAuth 1.0 implementation
pkg_oauth_homepage = https://github.com/tim/erlang-oauth
pkg_oauth_fetch = git
pkg_oauth_repo = https://github.com/tim/erlang-oauth
pkg_oauth_commit = master
PACKAGES += of_protocol
pkg_of_protocol_name = of_protocol
pkg_of_protocol_description = OpenFlow Protocol Library for Erlang
pkg_of_protocol_homepage = https://github.com/FlowForwarding/of_protocol
pkg_of_protocol_fetch = git
pkg_of_protocol_repo = https://github.com/FlowForwarding/of_protocol
pkg_of_protocol_commit = master
PACKAGES += openflow
pkg_openflow_name = openflow
pkg_openflow_description = An OpenFlow controller written in pure erlang
pkg_openflow_homepage = https://github.com/renatoaguiar/erlang-openflow
pkg_openflow_fetch = git
pkg_openflow_repo = https://github.com/renatoaguiar/erlang-openflow
pkg_openflow_commit = master
PACKAGES += openid
pkg_openid_name = openid
pkg_openid_description = Erlang OpenID
pkg_openid_homepage = https://github.com/brendonh/erl_openid
pkg_openid_fetch = git
pkg_openid_repo = https://github.com/brendonh/erl_openid
pkg_openid_commit = master
PACKAGES += openpoker
pkg_openpoker_name = openpoker
pkg_openpoker_description = Genesis Texas hold'em Game Server
pkg_openpoker_homepage = https://github.com/hpyhacking/openpoker
pkg_openpoker_fetch = git
pkg_openpoker_repo = https://github.com/hpyhacking/openpoker
pkg_openpoker_commit = master
PACKAGES += pal
pkg_pal_name = pal
pkg_pal_description = Pragmatic Authentication Library
pkg_pal_homepage = https://github.com/manifest/pal
pkg_pal_fetch = git
pkg_pal_repo = https://github.com/manifest/pal
pkg_pal_commit = master
PACKAGES += parse_trans
pkg_parse_trans_name = parse_trans
pkg_parse_trans_description = Parse transform utilities for Erlang
pkg_parse_trans_homepage = https://github.com/uwiger/parse_trans
pkg_parse_trans_fetch = git
pkg_parse_trans_repo = https://github.com/uwiger/parse_trans
pkg_parse_trans_commit = master
PACKAGES += parsexml
pkg_parsexml_name = parsexml
pkg_parsexml_description = Simple DOM XML parser with convenient and very simple API
pkg_parsexml_homepage = https://github.com/maxlapshin/parsexml
pkg_parsexml_fetch = git
pkg_parsexml_repo = https://github.com/maxlapshin/parsexml
pkg_parsexml_commit = master
PACKAGES += pegjs
pkg_pegjs_name = pegjs
pkg_pegjs_description = An implementation of PEG.js grammar for Erlang.
pkg_pegjs_homepage = https://github.com/dmitriid/pegjs
pkg_pegjs_fetch = git
pkg_pegjs_repo = https://github.com/dmitriid/pegjs
pkg_pegjs_commit = 0.3
PACKAGES += percept2
pkg_percept2_name = percept2
pkg_percept2_description = Concurrent profiling tool for Erlang
pkg_percept2_homepage = https://github.com/huiqing/percept2
pkg_percept2_fetch = git
pkg_percept2_repo = https://github.com/huiqing/percept2
pkg_percept2_commit = master
PACKAGES += pgsql
pkg_pgsql_name = pgsql
pkg_pgsql_description = Erlang PostgreSQL driver
pkg_pgsql_homepage = https://github.com/semiocast/pgsql
pkg_pgsql_fetch = git
pkg_pgsql_repo = https://github.com/semiocast/pgsql
pkg_pgsql_commit = master
PACKAGES += pkgx
pkg_pkgx_name = pkgx
pkg_pkgx_description = Build .deb packages from Erlang releases
pkg_pkgx_homepage = https://github.com/arjan/pkgx
pkg_pkgx_fetch = git
pkg_pkgx_repo = https://github.com/arjan/pkgx
pkg_pkgx_commit = master
PACKAGES += pkt
pkg_pkt_name = pkt
pkg_pkt_description = Erlang network protocol library
pkg_pkt_homepage = https://github.com/msantos/pkt
pkg_pkt_fetch = git
pkg_pkt_repo = https://github.com/msantos/pkt
pkg_pkt_commit = master
PACKAGES += plain_fsm
pkg_plain_fsm_name = plain_fsm
pkg_plain_fsm_description = A behaviour/support library for writing plain Erlang FSMs.
pkg_plain_fsm_homepage = https://github.com/uwiger/plain_fsm
pkg_plain_fsm_fetch = git
pkg_plain_fsm_repo = https://github.com/uwiger/plain_fsm
pkg_plain_fsm_commit = master
PACKAGES += plumtree
pkg_plumtree_name = plumtree
pkg_plumtree_description = Epidemic Broadcast Trees
pkg_plumtree_homepage = https://github.com/helium/plumtree
pkg_plumtree_fetch = git
pkg_plumtree_repo = https://github.com/helium/plumtree
pkg_plumtree_commit = master
PACKAGES += pmod_transform
pkg_pmod_transform_name = pmod_transform
pkg_pmod_transform_description = Parse transform for parameterized modules
pkg_pmod_transform_homepage = https://github.com/erlang/pmod_transform
pkg_pmod_transform_fetch = git
pkg_pmod_transform_repo = https://github.com/erlang/pmod_transform
pkg_pmod_transform_commit = master
PACKAGES += pobox
pkg_pobox_name = pobox
pkg_pobox_description = External buffer processes to protect against mailbox overflow in Erlang
pkg_pobox_homepage = https://github.com/ferd/pobox
pkg_pobox_fetch = git
pkg_pobox_repo = https://github.com/ferd/pobox
pkg_pobox_commit = master
PACKAGES += ponos
pkg_ponos_name = ponos
pkg_ponos_description = ponos is a simple yet powerful load generator written in erlang
pkg_ponos_homepage = https://github.com/klarna/ponos
pkg_ponos_fetch = git
pkg_ponos_repo = https://github.com/klarna/ponos
pkg_ponos_commit = master
PACKAGES += poolboy
pkg_poolboy_name = poolboy
pkg_poolboy_description = A hunky Erlang worker pool factory
pkg_poolboy_homepage = https://github.com/devinus/poolboy
pkg_poolboy_fetch = git
pkg_poolboy_repo = https://github.com/devinus/poolboy
pkg_poolboy_commit = master
PACKAGES += pooler
pkg_pooler_name = pooler
pkg_pooler_description = An OTP Process Pool Application
pkg_pooler_homepage = https://github.com/seth/pooler
pkg_pooler_fetch = git
pkg_pooler_repo = https://github.com/seth/pooler
pkg_pooler_commit = master
PACKAGES += pqueue
pkg_pqueue_name = pqueue
pkg_pqueue_description = Erlang Priority Queues
pkg_pqueue_homepage = https://github.com/okeuday/pqueue
pkg_pqueue_fetch = git
pkg_pqueue_repo = https://github.com/okeuday/pqueue
pkg_pqueue_commit = master
PACKAGES += procket
pkg_procket_name = procket
pkg_procket_description = Erlang interface to low level socket operations
pkg_procket_homepage = http://blog.listincomprehension.com/search/label/procket
pkg_procket_fetch = git
pkg_procket_repo = https://github.com/msantos/procket
pkg_procket_commit = master
PACKAGES += proper
pkg_proper_name = proper
pkg_proper_description = PropEr: a QuickCheck-inspired property-based testing tool for Erlang.
pkg_proper_homepage = http://proper.softlab.ntua.gr
pkg_proper_fetch = git
pkg_proper_repo = https://github.com/manopapad/proper
pkg_proper_commit = master
PACKAGES += prop
pkg_prop_name = prop
pkg_prop_description = An Erlang code scaffolding and generator system.
pkg_prop_homepage = https://github.com/nuex/prop
pkg_prop_fetch = git
pkg_prop_repo = https://github.com/nuex/prop
pkg_prop_commit = master
PACKAGES += props
pkg_props_name = props
pkg_props_description = Property structure library
pkg_props_homepage = https://github.com/greyarea/props
pkg_props_fetch = git
pkg_props_repo = https://github.com/greyarea/props
pkg_props_commit = master
PACKAGES += protobuffs
pkg_protobuffs_name = protobuffs
pkg_protobuffs_description = An implementation of Google's Protocol Buffers for Erlang, based on ngerakines/erlang_protobuffs.
pkg_protobuffs_homepage = https://github.com/basho/erlang_protobuffs
pkg_protobuffs_fetch = git
pkg_protobuffs_repo = https://github.com/basho/erlang_protobuffs
pkg_protobuffs_commit = master
PACKAGES += psycho
pkg_psycho_name = psycho
pkg_psycho_description = HTTP server that provides a WSGI-like interface for applications and middleware.
pkg_psycho_homepage = https://github.com/gar1t/psycho
pkg_psycho_fetch = git
pkg_psycho_repo = https://github.com/gar1t/psycho
pkg_psycho_commit = master
PACKAGES += ptrackerl
pkg_ptrackerl_name = ptrackerl
pkg_ptrackerl_description = Pivotal Tracker API Client written in Erlang
pkg_ptrackerl_homepage = https://github.com/inaka/ptrackerl
pkg_ptrackerl_fetch = git
pkg_ptrackerl_repo = https://github.com/inaka/ptrackerl
pkg_ptrackerl_commit = master
PACKAGES += purity
pkg_purity_name = purity
pkg_purity_description = A side-effect analyzer for Erlang
pkg_purity_homepage = https://github.com/mpitid/purity
pkg_purity_fetch = git
pkg_purity_repo = https://github.com/mpitid/purity
pkg_purity_commit = master
PACKAGES += push_service
pkg_push_service_name = push_service
pkg_push_service_description = Push service
pkg_push_service_homepage = https://github.com/hairyhum/push_service
pkg_push_service_fetch = git
pkg_push_service_repo = https://github.com/hairyhum/push_service
pkg_push_service_commit = master
PACKAGES += qdate
pkg_qdate_name = qdate
pkg_qdate_description = Date, time, and timezone parsing, formatting, and conversion for Erlang.
pkg_qdate_homepage = https://github.com/choptastic/qdate
pkg_qdate_fetch = git
pkg_qdate_repo = https://github.com/choptastic/qdate
pkg_qdate_commit = 0.4.0
PACKAGES += qrcode
pkg_qrcode_name = qrcode
pkg_qrcode_description = QR Code encoder in Erlang
pkg_qrcode_homepage = https://github.com/komone/qrcode
pkg_qrcode_fetch = git
pkg_qrcode_repo = https://github.com/komone/qrcode
pkg_qrcode_commit = master
PACKAGES += quest
pkg_quest_name = quest
pkg_quest_description = Learn Erlang through this set of challenges. An interactive system for getting to know Erlang.
pkg_quest_homepage = https://github.com/eriksoe/ErlangQuest
pkg_quest_fetch = git
pkg_quest_repo = https://github.com/eriksoe/ErlangQuest
pkg_quest_commit = master
PACKAGES += quickrand
pkg_quickrand_name = quickrand
pkg_quickrand_description = Quick Erlang Random Number Generation
pkg_quickrand_homepage = https://github.com/okeuday/quickrand
pkg_quickrand_fetch = git
pkg_quickrand_repo = https://github.com/okeuday/quickrand
pkg_quickrand_commit = master
PACKAGES += rabbit_exchange_type_riak
pkg_rabbit_exchange_type_riak_name = rabbit_exchange_type_riak
pkg_rabbit_exchange_type_riak_description = Custom RabbitMQ exchange type for sticking messages in Riak
pkg_rabbit_exchange_type_riak_homepage = https://github.com/jbrisbin/riak-exchange
pkg_rabbit_exchange_type_riak_fetch = git
pkg_rabbit_exchange_type_riak_repo = https://github.com/jbrisbin/riak-exchange
pkg_rabbit_exchange_type_riak_commit = master
PACKAGES += rabbit
pkg_rabbit_name = rabbit
pkg_rabbit_description = RabbitMQ Server
pkg_rabbit_homepage = https://www.rabbitmq.com/
pkg_rabbit_fetch = git
pkg_rabbit_repo = https://github.com/rabbitmq/rabbitmq-server.git
pkg_rabbit_commit = master
PACKAGES += rack
pkg_rack_name = rack
pkg_rack_description = Rack handler for erlang
pkg_rack_homepage = https://github.com/erlyvideo/rack
pkg_rack_fetch = git
pkg_rack_repo = https://github.com/erlyvideo/rack
pkg_rack_commit = master
PACKAGES += radierl
pkg_radierl_name = radierl
pkg_radierl_description = RADIUS protocol stack implemented in Erlang.
pkg_radierl_homepage = https://github.com/vances/radierl
pkg_radierl_fetch = git
pkg_radierl_repo = https://github.com/vances/radierl
pkg_radierl_commit = master
PACKAGES += rafter
pkg_rafter_name = rafter
pkg_rafter_description = An Erlang library application which implements the Raft consensus protocol
pkg_rafter_homepage = https://github.com/andrewjstone/rafter
pkg_rafter_fetch = git
pkg_rafter_repo = https://github.com/andrewjstone/rafter
pkg_rafter_commit = master
PACKAGES += ranch
pkg_ranch_name = ranch
pkg_ranch_description = Socket acceptor pool for TCP protocols.
pkg_ranch_homepage = http://ninenines.eu
pkg_ranch_fetch = git
pkg_ranch_repo = https://github.com/ninenines/ranch
pkg_ranch_commit = 1.1.0
PACKAGES += rbeacon
pkg_rbeacon_name = rbeacon
pkg_rbeacon_description = LAN discovery and presence in Erlang.
pkg_rbeacon_homepage = https://github.com/refuge/rbeacon
pkg_rbeacon_fetch = git
pkg_rbeacon_repo = https://github.com/refuge/rbeacon
pkg_rbeacon_commit = master
PACKAGES += rebar
pkg_rebar_name = rebar
pkg_rebar_description = Erlang build tool that makes it easy to compile and test Erlang applications, port drivers and releases.
pkg_rebar_homepage = http://www.rebar3.org
pkg_rebar_fetch = git
pkg_rebar_repo = https://github.com/rebar/rebar3
pkg_rebar_commit = master
PACKAGES += rebus
pkg_rebus_name = rebus
pkg_rebus_description = A stupid simple, internal, pub/sub event bus written in- and for Erlang.
pkg_rebus_homepage = https://github.com/olle/rebus
pkg_rebus_fetch = git
pkg_rebus_repo = https://github.com/olle/rebus
pkg_rebus_commit = master
PACKAGES += rec2json
pkg_rec2json_name = rec2json
pkg_rec2json_description = Compile erlang record definitions into modules to convert them to/from json easily.
pkg_rec2json_homepage = https://github.com/lordnull/rec2json
pkg_rec2json_fetch = git
pkg_rec2json_repo = https://github.com/lordnull/rec2json
pkg_rec2json_commit = master
PACKAGES += recon
pkg_recon_name = recon
pkg_recon_description = Collection of functions and scripts to debug Erlang in production.
pkg_recon_homepage = https://github.com/ferd/recon
pkg_recon_fetch = git
pkg_recon_repo = https://github.com/ferd/recon
pkg_recon_commit = 2.2.1
PACKAGES += record_info
pkg_record_info_name = record_info
pkg_record_info_description = Convert between record and proplist
pkg_record_info_homepage = https://github.com/bipthelin/erlang-record_info
pkg_record_info_fetch = git
pkg_record_info_repo = https://github.com/bipthelin/erlang-record_info
pkg_record_info_commit = master
PACKAGES += redgrid
pkg_redgrid_name = redgrid
pkg_redgrid_description = automatic Erlang node discovery via redis
pkg_redgrid_homepage = https://github.com/jkvor/redgrid
pkg_redgrid_fetch = git
pkg_redgrid_repo = https://github.com/jkvor/redgrid
pkg_redgrid_commit = master
PACKAGES += redo
pkg_redo_name = redo
pkg_redo_description = pipelined erlang redis client
pkg_redo_homepage = https://github.com/jkvor/redo
pkg_redo_fetch = git
pkg_redo_repo = https://github.com/jkvor/redo
pkg_redo_commit = master
PACKAGES += reltool_util
pkg_reltool_util_name = reltool_util
pkg_reltool_util_description = Erlang reltool utility functionality application
pkg_reltool_util_homepage = https://github.com/okeuday/reltool_util
pkg_reltool_util_fetch = git
pkg_reltool_util_repo = https://github.com/okeuday/reltool_util
pkg_reltool_util_commit = master
PACKAGES += relx
pkg_relx_name = relx
pkg_relx_description = Sane, simple release creation for Erlang
pkg_relx_homepage = https://github.com/erlware/relx
pkg_relx_fetch = git
pkg_relx_repo = https://github.com/erlware/relx
pkg_relx_commit = master
PACKAGES += resource_discovery
pkg_resource_discovery_name = resource_discovery
pkg_resource_discovery_description = An application used to dynamically discover resources present in an Erlang node cluster.
pkg_resource_discovery_homepage = http://erlware.org/
pkg_resource_discovery_fetch = git
pkg_resource_discovery_repo = https://github.com/erlware/resource_discovery
pkg_resource_discovery_commit = master
PACKAGES += restc
pkg_restc_name = restc
pkg_restc_description = Erlang Rest Client
pkg_restc_homepage = https://github.com/kivra/restclient
pkg_restc_fetch = git
pkg_restc_repo = https://github.com/kivra/restclient
pkg_restc_commit = master
PACKAGES += rfc4627_jsonrpc
pkg_rfc4627_jsonrpc_name = rfc4627_jsonrpc
pkg_rfc4627_jsonrpc_description = Erlang RFC4627 (JSON) codec and JSON-RPC server implementation.
pkg_rfc4627_jsonrpc_homepage = https://github.com/tonyg/erlang-rfc4627
pkg_rfc4627_jsonrpc_fetch = git
pkg_rfc4627_jsonrpc_repo = https://github.com/tonyg/erlang-rfc4627
pkg_rfc4627_jsonrpc_commit = master
PACKAGES += riakc
pkg_riakc_name = riakc
pkg_riakc_description = Erlang clients for Riak.
pkg_riakc_homepage = https://github.com/basho/riak-erlang-client
pkg_riakc_fetch = git
pkg_riakc_repo = https://github.com/basho/riak-erlang-client
pkg_riakc_commit = master
PACKAGES += riak_core
pkg_riak_core_name = riak_core
pkg_riak_core_description = Distributed systems infrastructure used by Riak.
pkg_riak_core_homepage = https://github.com/basho/riak_core
pkg_riak_core_fetch = git
pkg_riak_core_repo = https://github.com/basho/riak_core
pkg_riak_core_commit = master
PACKAGES += riak_dt
pkg_riak_dt_name = riak_dt
pkg_riak_dt_description = Convergent replicated datatypes in Erlang
pkg_riak_dt_homepage = https://github.com/basho/riak_dt
pkg_riak_dt_fetch = git
pkg_riak_dt_repo = https://github.com/basho/riak_dt
pkg_riak_dt_commit = master
PACKAGES += riak_ensemble
pkg_riak_ensemble_name = riak_ensemble
pkg_riak_ensemble_description = Multi-Paxos framework in Erlang
pkg_riak_ensemble_homepage = https://github.com/basho/riak_ensemble
pkg_riak_ensemble_fetch = git
pkg_riak_ensemble_repo = https://github.com/basho/riak_ensemble
pkg_riak_ensemble_commit = master
PACKAGES += riakhttpc
pkg_riakhttpc_name = riakhttpc
pkg_riakhttpc_description = Riak Erlang client using the HTTP interface
pkg_riakhttpc_homepage = https://github.com/basho/riak-erlang-http-client
pkg_riakhttpc_fetch = git
pkg_riakhttpc_repo = https://github.com/basho/riak-erlang-http-client
pkg_riakhttpc_commit = master
PACKAGES += riak_kv
pkg_riak_kv_name = riak_kv
pkg_riak_kv_description = Riak Key/Value Store
pkg_riak_kv_homepage = https://github.com/basho/riak_kv
pkg_riak_kv_fetch = git
pkg_riak_kv_repo = https://github.com/basho/riak_kv
pkg_riak_kv_commit = master
PACKAGES += riaknostic
pkg_riaknostic_name = riaknostic
pkg_riaknostic_description = A diagnostic tool for Riak installations, to find common errors asap
pkg_riaknostic_homepage = https://github.com/basho/riaknostic
pkg_riaknostic_fetch = git
pkg_riaknostic_repo = https://github.com/basho/riaknostic
pkg_riaknostic_commit = master
PACKAGES += riak_pg
pkg_riak_pg_name = riak_pg
pkg_riak_pg_description = Distributed process groups with riak_core.
pkg_riak_pg_homepage = https://github.com/cmeiklejohn/riak_pg
pkg_riak_pg_fetch = git
pkg_riak_pg_repo = https://github.com/cmeiklejohn/riak_pg
pkg_riak_pg_commit = master
PACKAGES += riak_pipe
pkg_riak_pipe_name = riak_pipe
pkg_riak_pipe_description = Riak Pipelines
pkg_riak_pipe_homepage = https://github.com/basho/riak_pipe
pkg_riak_pipe_fetch = git
pkg_riak_pipe_repo = https://github.com/basho/riak_pipe
pkg_riak_pipe_commit = master
PACKAGES += riakpool
pkg_riakpool_name = riakpool
pkg_riakpool_description = erlang riak client pool
pkg_riakpool_homepage = https://github.com/dweldon/riakpool
pkg_riakpool_fetch = git
pkg_riakpool_repo = https://github.com/dweldon/riakpool
pkg_riakpool_commit = master
PACKAGES += riak_sysmon
pkg_riak_sysmon_name = riak_sysmon
pkg_riak_sysmon_description = Simple OTP app for managing Erlang VM system_monitor event messages
pkg_riak_sysmon_homepage = https://github.com/basho/riak_sysmon
pkg_riak_sysmon_fetch = git
pkg_riak_sysmon_repo = https://github.com/basho/riak_sysmon
pkg_riak_sysmon_commit = master
PACKAGES += riak_test
pkg_riak_test_name = riak_test
pkg_riak_test_description = I'm in your cluster, testing your riaks
pkg_riak_test_homepage = https://github.com/basho/riak_test
pkg_riak_test_fetch = git
pkg_riak_test_repo = https://github.com/basho/riak_test
pkg_riak_test_commit = master
PACKAGES += rivus_cep
pkg_rivus_cep_name = rivus_cep
pkg_rivus_cep_description = Complex event processing in Erlang
pkg_rivus_cep_homepage = https://github.com/vascokk/rivus_cep
pkg_rivus_cep_fetch = git
pkg_rivus_cep_repo = https://github.com/vascokk/rivus_cep
pkg_rivus_cep_commit = master
PACKAGES += rlimit
pkg_rlimit_name = rlimit
pkg_rlimit_description = Magnus Klaar's rate limiter code from etorrent
pkg_rlimit_homepage = https://github.com/jlouis/rlimit
pkg_rlimit_fetch = git
pkg_rlimit_repo = https://github.com/jlouis/rlimit
pkg_rlimit_commit = master
PACKAGES += safetyvalve
pkg_safetyvalve_name = safetyvalve
pkg_safetyvalve_description = A safety valve for your erlang node
pkg_safetyvalve_homepage = https://github.com/jlouis/safetyvalve
pkg_safetyvalve_fetch = git
pkg_safetyvalve_repo = https://github.com/jlouis/safetyvalve
pkg_safetyvalve_commit = master
PACKAGES += seestar
pkg_seestar_name = seestar
pkg_seestar_description = The Erlang client for Cassandra 1.2+ binary protocol
pkg_seestar_homepage = https://github.com/iamaleksey/seestar
pkg_seestar_fetch = git
pkg_seestar_repo = https://github.com/iamaleksey/seestar
pkg_seestar_commit = master
PACKAGES += service
pkg_service_name = service
pkg_service_description = A minimal Erlang behavior for creating CloudI internal services
pkg_service_homepage = http://cloudi.org/
pkg_service_fetch = git
pkg_service_repo = https://github.com/CloudI/service
pkg_service_commit = master
PACKAGES += setup
pkg_setup_name = setup
pkg_setup_description = Generic setup utility for Erlang-based systems
pkg_setup_homepage = https://github.com/uwiger/setup
pkg_setup_fetch = git
pkg_setup_repo = https://github.com/uwiger/setup
pkg_setup_commit = master
PACKAGES += sext
pkg_sext_name = sext
pkg_sext_description = Sortable Erlang Term Serialization
pkg_sext_homepage = https://github.com/uwiger/sext
pkg_sext_fetch = git
pkg_sext_repo = https://github.com/uwiger/sext
pkg_sext_commit = master
PACKAGES += sfmt
pkg_sfmt_name = sfmt
pkg_sfmt_description = SFMT pseudo random number generator for Erlang.
pkg_sfmt_homepage = https://github.com/jj1bdx/sfmt-erlang
pkg_sfmt_fetch = git
pkg_sfmt_repo = https://github.com/jj1bdx/sfmt-erlang
pkg_sfmt_commit = master
PACKAGES += sgte
pkg_sgte_name = sgte
pkg_sgte_description = A simple Erlang Template Engine
pkg_sgte_homepage = https://github.com/filippo/sgte
pkg_sgte_fetch = git
pkg_sgte_repo = https://github.com/filippo/sgte
pkg_sgte_commit = master
PACKAGES += sheriff
pkg_sheriff_name = sheriff
pkg_sheriff_description = Parse transform for type based validation.
pkg_sheriff_homepage = http://ninenines.eu
pkg_sheriff_fetch = git
pkg_sheriff_repo = https://github.com/extend/sheriff
pkg_sheriff_commit = master
PACKAGES += shotgun
pkg_shotgun_name = shotgun
pkg_shotgun_description = better than just a gun
pkg_shotgun_homepage = https://github.com/inaka/shotgun
pkg_shotgun_fetch = git
pkg_shotgun_repo = https://github.com/inaka/shotgun
pkg_shotgun_commit = 0.1.0
PACKAGES += sidejob
pkg_sidejob_name = sidejob
pkg_sidejob_description = Parallel worker and capacity limiting library for Erlang
pkg_sidejob_homepage = https://github.com/basho/sidejob
pkg_sidejob_fetch = git
pkg_sidejob_repo = https://github.com/basho/sidejob
pkg_sidejob_commit = master
PACKAGES += sieve
pkg_sieve_name = sieve
pkg_sieve_description = sieve is a simple TCP routing proxy (layer 7) in erlang
pkg_sieve_homepage = https://github.com/benoitc/sieve
pkg_sieve_fetch = git
pkg_sieve_repo = https://github.com/benoitc/sieve
pkg_sieve_commit = master
PACKAGES += sighandler
pkg_sighandler_name = sighandler
pkg_sighandler_description = Handle UNIX signals in Er lang
pkg_sighandler_homepage = https://github.com/jkingsbery/sighandler
pkg_sighandler_fetch = git
pkg_sighandler_repo = https://github.com/jkingsbery/sighandler
pkg_sighandler_commit = master
PACKAGES += simhash
pkg_simhash_name = simhash
pkg_simhash_description = Simhashing for Erlang -- hashing algorithm to find near-duplicates in binary data.
pkg_simhash_homepage = https://github.com/ferd/simhash
pkg_simhash_fetch = git
pkg_simhash_repo = https://github.com/ferd/simhash
pkg_simhash_commit = master
PACKAGES += simple_bridge
pkg_simple_bridge_name = simple_bridge
pkg_simple_bridge_description = A simple, standardized interface library to Erlang HTTP Servers.
pkg_simple_bridge_homepage = https://github.com/nitrogen/simple_bridge
pkg_simple_bridge_fetch = git
pkg_simple_bridge_repo = https://github.com/nitrogen/simple_bridge
pkg_simple_bridge_commit = master
PACKAGES += simple_oauth2
pkg_simple_oauth2_name = simple_oauth2
pkg_simple_oauth2_description = Simple erlang OAuth2 client module for any http server framework (Google, Facebook, Yandex, Vkontakte are preconfigured)
pkg_simple_oauth2_homepage = https://github.com/virtan/simple_oauth2
pkg_simple_oauth2_fetch = git
pkg_simple_oauth2_repo = https://github.com/virtan/simple_oauth2
pkg_simple_oauth2_commit = master
PACKAGES += skel
pkg_skel_name = skel
pkg_skel_description = A Streaming Process-based Skeleton Library for Erlang
pkg_skel_homepage = https://github.com/ParaPhrase/skel
pkg_skel_fetch = git
pkg_skel_repo = https://github.com/ParaPhrase/skel
pkg_skel_commit = master
PACKAGES += smother
pkg_smother_name = smother
pkg_smother_description = Extended code coverage metrics for Erlang.
pkg_smother_homepage = https://ramsay-t.github.io/Smother/
pkg_smother_fetch = git
pkg_smother_repo = https://github.com/ramsay-t/Smother
pkg_smother_commit = master
PACKAGES += social
pkg_social_name = social
pkg_social_description = Cowboy handler for social login via OAuth2 providers
pkg_social_homepage = https://github.com/dvv/social
pkg_social_fetch = git
pkg_social_repo = https://github.com/dvv/social
pkg_social_commit = master
PACKAGES += spapi_router
pkg_spapi_router_name = spapi_router
pkg_spapi_router_description = Partially-connected Erlang clustering
pkg_spapi_router_homepage = https://github.com/spilgames/spapi-router
pkg_spapi_router_fetch = git
pkg_spapi_router_repo = https://github.com/spilgames/spapi-router
pkg_spapi_router_commit = master
PACKAGES += sqerl
pkg_sqerl_name = sqerl
pkg_sqerl_description = An Erlang-flavoured SQL DSL
pkg_sqerl_homepage = https://github.com/hairyhum/sqerl
pkg_sqerl_fetch = git
pkg_sqerl_repo = https://github.com/hairyhum/sqerl
pkg_sqerl_commit = master
PACKAGES += srly
pkg_srly_name = srly
pkg_srly_description = Native Erlang Unix serial interface
pkg_srly_homepage = https://github.com/msantos/srly
pkg_srly_fetch = git
pkg_srly_repo = https://github.com/msantos/srly
pkg_srly_commit = master
PACKAGES += sshrpc
pkg_sshrpc_name = sshrpc
pkg_sshrpc_description = Erlang SSH RPC module (experimental)
pkg_sshrpc_homepage = https://github.com/jj1bdx/sshrpc
pkg_sshrpc_fetch = git
pkg_sshrpc_repo = https://github.com/jj1bdx/sshrpc
pkg_sshrpc_commit = master
PACKAGES += stable
pkg_stable_name = stable
pkg_stable_description = Library of assorted helpers for Cowboy web server.
pkg_stable_homepage = https://github.com/dvv/stable
pkg_stable_fetch = git
pkg_stable_repo = https://github.com/dvv/stable
pkg_stable_commit = master
PACKAGES += statebox
pkg_statebox_name = statebox
pkg_statebox_description = Erlang state monad with merge/conflict-resolution capabilities. Useful for Riak.
pkg_statebox_homepage = https://github.com/mochi/statebox
pkg_statebox_fetch = git
pkg_statebox_repo = https://github.com/mochi/statebox
pkg_statebox_commit = master
PACKAGES += statebox_riak
pkg_statebox_riak_name = statebox_riak
pkg_statebox_riak_description = Convenience library that makes it easier to use statebox with riak, extracted from best practices in our production code at Mochi Media.
pkg_statebox_riak_homepage = https://github.com/mochi/statebox_riak
pkg_statebox_riak_fetch = git
pkg_statebox_riak_repo = https://github.com/mochi/statebox_riak
pkg_statebox_riak_commit = master
PACKAGES += statman
pkg_statman_name = statman
pkg_statman_description = Efficiently collect massive volumes of metrics inside the Erlang VM
pkg_statman_homepage = https://github.com/knutin/statman
pkg_statman_fetch = git
pkg_statman_repo = https://github.com/knutin/statman
pkg_statman_commit = master
PACKAGES += statsderl
pkg_statsderl_name = statsderl
pkg_statsderl_description = StatsD client (erlang)
pkg_statsderl_homepage = https://github.com/lpgauth/statsderl
pkg_statsderl_fetch = git
pkg_statsderl_repo = https://github.com/lpgauth/statsderl
pkg_statsderl_commit = master
PACKAGES += stdinout_pool
pkg_stdinout_pool_name = stdinout_pool
pkg_stdinout_pool_description = stdinout_pool : stuff goes in, stuff goes out. there's never any miscommunication.
pkg_stdinout_pool_homepage = https://github.com/mattsta/erlang-stdinout-pool
pkg_stdinout_pool_fetch = git
pkg_stdinout_pool_repo = https://github.com/mattsta/erlang-stdinout-pool
pkg_stdinout_pool_commit = master
PACKAGES += stockdb
pkg_stockdb_name = stockdb
pkg_stockdb_description = Database for storing Stock Exchange quotes in erlang
pkg_stockdb_homepage = https://github.com/maxlapshin/stockdb
pkg_stockdb_fetch = git
pkg_stockdb_repo = https://github.com/maxlapshin/stockdb
pkg_stockdb_commit = master
PACKAGES += stripe
pkg_stripe_name = stripe
pkg_stripe_description = Erlang interface to the stripe.com API
pkg_stripe_homepage = https://github.com/mattsta/stripe-erlang
pkg_stripe_fetch = git
pkg_stripe_repo = https://github.com/mattsta/stripe-erlang
pkg_stripe_commit = v1
PACKAGES += surrogate
pkg_surrogate_name = surrogate
pkg_surrogate_description = Proxy server written in erlang. Supports reverse proxy load balancing and forward proxy with http (including CONNECT), socks4, socks5, and transparent proxy modes.
pkg_surrogate_homepage = https://github.com/skruger/Surrogate
pkg_surrogate_fetch = git
pkg_surrogate_repo = https://github.com/skruger/Surrogate
pkg_surrogate_commit = master
PACKAGES += swab
pkg_swab_name = swab
pkg_swab_description = General purpose buffer handling module
pkg_swab_homepage = https://github.com/crownedgrouse/swab
pkg_swab_fetch = git
pkg_swab_repo = https://github.com/crownedgrouse/swab
pkg_swab_commit = master
PACKAGES += swarm
pkg_swarm_name = swarm
pkg_swarm_description = Fast and simple acceptor pool for Erlang
pkg_swarm_homepage = https://github.com/jeremey/swarm
pkg_swarm_fetch = git
pkg_swarm_repo = https://github.com/jeremey/swarm
pkg_swarm_commit = master
PACKAGES += switchboard
pkg_switchboard_name = switchboard
pkg_switchboard_description = A framework for processing email using worker plugins.
pkg_switchboard_homepage = https://github.com/thusfresh/switchboard
pkg_switchboard_fetch = git
pkg_switchboard_repo = https://github.com/thusfresh/switchboard
pkg_switchboard_commit = master
PACKAGES += sync
pkg_sync_name = sync
pkg_sync_description = On-the-fly recompiling and reloading in Erlang.
pkg_sync_homepage = https://github.com/rustyio/sync
pkg_sync_fetch = git
pkg_sync_repo = https://github.com/rustyio/sync
pkg_sync_commit = master
PACKAGES += syn
pkg_syn_name = syn
pkg_syn_description = A global process registry for Erlang.
pkg_syn_homepage = https://github.com/ostinelli/syn
pkg_syn_fetch = git
pkg_syn_repo = https://github.com/ostinelli/syn
pkg_syn_commit = master
PACKAGES += syntaxerl
pkg_syntaxerl_name = syntaxerl
pkg_syntaxerl_description = Syntax checker for Erlang
pkg_syntaxerl_homepage = https://github.com/ten0s/syntaxerl
pkg_syntaxerl_fetch = git
pkg_syntaxerl_repo = https://github.com/ten0s/syntaxerl
pkg_syntaxerl_commit = master
PACKAGES += syslog
pkg_syslog_name = syslog
pkg_syslog_description = Erlang port driver for interacting with syslog via syslog(3)
pkg_syslog_homepage = https://github.com/Vagabond/erlang-syslog
pkg_syslog_fetch = git
pkg_syslog_repo = https://github.com/Vagabond/erlang-syslog
pkg_syslog_commit = master
PACKAGES += taskforce
pkg_taskforce_name = taskforce
pkg_taskforce_description = Erlang worker pools for controlled parallelisation of arbitrary tasks.
pkg_taskforce_homepage = https://github.com/g-andrade/taskforce
pkg_taskforce_fetch = git
pkg_taskforce_repo = https://github.com/g-andrade/taskforce
pkg_taskforce_commit = master
PACKAGES += tddreloader
pkg_tddreloader_name = tddreloader
pkg_tddreloader_description = Shell utility for recompiling, reloading, and testing code as it changes
pkg_tddreloader_homepage = https://github.com/version2beta/tddreloader
pkg_tddreloader_fetch = git
pkg_tddreloader_repo = https://github.com/version2beta/tddreloader
pkg_tddreloader_commit = master
PACKAGES += tempo
pkg_tempo_name = tempo
pkg_tempo_description = NIF-based date and time parsing and formatting for Erlang.
pkg_tempo_homepage = https://github.com/selectel/tempo
pkg_tempo_fetch = git
pkg_tempo_repo = https://github.com/selectel/tempo
pkg_tempo_commit = master
PACKAGES += ticktick
pkg_ticktick_name = ticktick
pkg_ticktick_description = Ticktick is an id generator for message service.
pkg_ticktick_homepage = https://github.com/ericliang/ticktick
pkg_ticktick_fetch = git
pkg_ticktick_repo = https://github.com/ericliang/ticktick
pkg_ticktick_commit = master
PACKAGES += tinymq
pkg_tinymq_name = tinymq
pkg_tinymq_description = TinyMQ - a diminutive, in-memory message queue
pkg_tinymq_homepage = https://github.com/ChicagoBoss/tinymq
pkg_tinymq_fetch = git
pkg_tinymq_repo = https://github.com/ChicagoBoss/tinymq
pkg_tinymq_commit = master
PACKAGES += tinymt
pkg_tinymt_name = tinymt
pkg_tinymt_description = TinyMT pseudo random number generator for Erlang.
pkg_tinymt_homepage = https://github.com/jj1bdx/tinymt-erlang
pkg_tinymt_fetch = git
pkg_tinymt_repo = https://github.com/jj1bdx/tinymt-erlang
pkg_tinymt_commit = master
PACKAGES += tirerl
pkg_tirerl_name = tirerl
pkg_tirerl_description = Erlang interface to Elastic Search
pkg_tirerl_homepage = https://github.com/inaka/tirerl
pkg_tirerl_fetch = git
pkg_tirerl_repo = https://github.com/inaka/tirerl
pkg_tirerl_commit = master
PACKAGES += traffic_tools
pkg_traffic_tools_name = traffic_tools
pkg_traffic_tools_description = Simple traffic limiting library
pkg_traffic_tools_homepage = https://github.com/systra/traffic_tools
pkg_traffic_tools_fetch = git
pkg_traffic_tools_repo = https://github.com/systra/traffic_tools
pkg_traffic_tools_commit = master
PACKAGES += trails
pkg_trails_name = trails
pkg_trails_description = A couple of improvements over Cowboy Routes
pkg_trails_homepage = http://inaka.github.io/cowboy-trails/
pkg_trails_fetch = git
pkg_trails_repo = https://github.com/inaka/cowboy-trails
pkg_trails_commit = master
PACKAGES += trane
pkg_trane_name = trane
pkg_trane_description = SAX style broken HTML parser in Erlang
pkg_trane_homepage = https://github.com/massemanet/trane
pkg_trane_fetch = git
pkg_trane_repo = https://github.com/massemanet/trane
pkg_trane_commit = master
PACKAGES += transit
pkg_transit_name = transit
pkg_transit_description = transit format for erlang
pkg_transit_homepage = https://github.com/isaiah/transit-erlang
pkg_transit_fetch = git
pkg_transit_repo = https://github.com/isaiah/transit-erlang
pkg_transit_commit = master
PACKAGES += trie
pkg_trie_name = trie
pkg_trie_description = Erlang Trie Implementation
pkg_trie_homepage = https://github.com/okeuday/trie
pkg_trie_fetch = git
pkg_trie_repo = https://github.com/okeuday/trie
pkg_trie_commit = master
PACKAGES += triq
pkg_triq_name = triq
pkg_triq_description = Trifork QuickCheck
pkg_triq_homepage = https://github.com/krestenkrab/triq
pkg_triq_fetch = git
pkg_triq_repo = https://github.com/krestenkrab/triq
pkg_triq_commit = master
PACKAGES += tunctl
pkg_tunctl_name = tunctl
pkg_tunctl_description = Erlang TUN/TAP interface
pkg_tunctl_homepage = https://github.com/msantos/tunctl
pkg_tunctl_fetch = git
pkg_tunctl_repo = https://github.com/msantos/tunctl
pkg_tunctl_commit = master
PACKAGES += twerl
pkg_twerl_name = twerl
pkg_twerl_description = Erlang client for the Twitter Streaming API
pkg_twerl_homepage = https://github.com/lucaspiller/twerl
pkg_twerl_fetch = git
pkg_twerl_repo = https://github.com/lucaspiller/twerl
pkg_twerl_commit = oauth
PACKAGES += twitter_erlang
pkg_twitter_erlang_name = twitter_erlang
pkg_twitter_erlang_description = An Erlang twitter client
pkg_twitter_erlang_homepage = https://github.com/ngerakines/erlang_twitter
pkg_twitter_erlang_fetch = git
pkg_twitter_erlang_repo = https://github.com/ngerakines/erlang_twitter
pkg_twitter_erlang_commit = master
PACKAGES += ucol_nif
pkg_ucol_nif_name = ucol_nif
pkg_ucol_nif_description = ICU based collation Erlang module
pkg_ucol_nif_homepage = https://github.com/refuge/ucol_nif
pkg_ucol_nif_fetch = git
pkg_ucol_nif_repo = https://github.com/refuge/ucol_nif
pkg_ucol_nif_commit = master
PACKAGES += unicorn
pkg_unicorn_name = unicorn
pkg_unicorn_description = Generic configuration server
pkg_unicorn_homepage = https://github.com/shizzard/unicorn
pkg_unicorn_fetch = git
pkg_unicorn_repo = https://github.com/shizzard/unicorn
pkg_unicorn_commit = 0.3.0
PACKAGES += unsplit
pkg_unsplit_name = unsplit
pkg_unsplit_description = Resolves conflicts in Mnesia after network splits
pkg_unsplit_homepage = https://github.com/uwiger/unsplit
pkg_unsplit_fetch = git
pkg_unsplit_repo = https://github.com/uwiger/unsplit
pkg_unsplit_commit = master
PACKAGES += uuid
pkg_uuid_name = uuid
pkg_uuid_description = Erlang UUID Implementation
pkg_uuid_homepage = https://github.com/okeuday/uuid
pkg_uuid_fetch = git
pkg_uuid_repo = https://github.com/okeuday/uuid
pkg_uuid_commit = v1.4.0
PACKAGES += ux
pkg_ux_name = ux
pkg_ux_description = Unicode eXtention for Erlang (Strings, Collation)
pkg_ux_homepage = https://github.com/erlang-unicode/ux
pkg_ux_fetch = git
pkg_ux_repo = https://github.com/erlang-unicode/ux
pkg_ux_commit = master
PACKAGES += vert
pkg_vert_name = vert
pkg_vert_description = Erlang binding to libvirt virtualization API
pkg_vert_homepage = https://github.com/msantos/erlang-libvirt
pkg_vert_fetch = git
pkg_vert_repo = https://github.com/msantos/erlang-libvirt
pkg_vert_commit = master
PACKAGES += verx
pkg_verx_name = verx
pkg_verx_description = Erlang implementation of the libvirtd remote protocol
pkg_verx_homepage = https://github.com/msantos/verx
pkg_verx_fetch = git
pkg_verx_repo = https://github.com/msantos/verx
pkg_verx_commit = master
PACKAGES += vmq_acl
pkg_vmq_acl_name = vmq_acl
pkg_vmq_acl_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_acl_homepage = https://verne.mq/
pkg_vmq_acl_fetch = git
pkg_vmq_acl_repo = https://github.com/erlio/vmq_acl
pkg_vmq_acl_commit = master
PACKAGES += vmq_bridge
pkg_vmq_bridge_name = vmq_bridge
pkg_vmq_bridge_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_bridge_homepage = https://verne.mq/
pkg_vmq_bridge_fetch = git
pkg_vmq_bridge_repo = https://github.com/erlio/vmq_bridge
pkg_vmq_bridge_commit = master
PACKAGES += vmq_graphite
pkg_vmq_graphite_name = vmq_graphite
pkg_vmq_graphite_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_graphite_homepage = https://verne.mq/
pkg_vmq_graphite_fetch = git
pkg_vmq_graphite_repo = https://github.com/erlio/vmq_graphite
pkg_vmq_graphite_commit = master
PACKAGES += vmq_passwd
pkg_vmq_passwd_name = vmq_passwd
pkg_vmq_passwd_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_passwd_homepage = https://verne.mq/
pkg_vmq_passwd_fetch = git
pkg_vmq_passwd_repo = https://github.com/erlio/vmq_passwd
pkg_vmq_passwd_commit = master
PACKAGES += vmq_server
pkg_vmq_server_name = vmq_server
pkg_vmq_server_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_server_homepage = https://verne.mq/
pkg_vmq_server_fetch = git
pkg_vmq_server_repo = https://github.com/erlio/vmq_server
pkg_vmq_server_commit = master
PACKAGES += vmq_snmp
pkg_vmq_snmp_name = vmq_snmp
pkg_vmq_snmp_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_snmp_homepage = https://verne.mq/
pkg_vmq_snmp_fetch = git
pkg_vmq_snmp_repo = https://github.com/erlio/vmq_snmp
pkg_vmq_snmp_commit = master
PACKAGES += vmq_systree
pkg_vmq_systree_name = vmq_systree
pkg_vmq_systree_description = Component of VerneMQ: A distributed MQTT message broker
pkg_vmq_systree_homepage = https://verne.mq/
pkg_vmq_systree_fetch = git
pkg_vmq_systree_repo = https://github.com/erlio/vmq_systree
pkg_vmq_systree_commit = master
PACKAGES += vmstats
pkg_vmstats_name = vmstats
pkg_vmstats_description = tiny Erlang app that works in conjunction with statsderl in order to generate information on the Erlang VM for graphite logs.
pkg_vmstats_homepage = https://github.com/ferd/vmstats
pkg_vmstats_fetch = git
pkg_vmstats_repo = https://github.com/ferd/vmstats
pkg_vmstats_commit = master
PACKAGES += walrus
pkg_walrus_name = walrus
pkg_walrus_description = Walrus - Mustache-like Templating
pkg_walrus_homepage = https://github.com/devinus/walrus
pkg_walrus_fetch = git
pkg_walrus_repo = https://github.com/devinus/walrus
pkg_walrus_commit = master
PACKAGES += webmachine
pkg_webmachine_name = webmachine
pkg_webmachine_description = A REST-based system for building web applications.
pkg_webmachine_homepage = https://github.com/basho/webmachine
pkg_webmachine_fetch = git
pkg_webmachine_repo = https://github.com/basho/webmachine
pkg_webmachine_commit = master
PACKAGES += websocket_client
pkg_websocket_client_name = websocket_client
pkg_websocket_client_description = Erlang websocket client (ws and wss supported)
pkg_websocket_client_homepage = https://github.com/jeremyong/websocket_client
pkg_websocket_client_fetch = git
pkg_websocket_client_repo = https://github.com/jeremyong/websocket_client
pkg_websocket_client_commit = master
PACKAGES += worker_pool
pkg_worker_pool_name = worker_pool
pkg_worker_pool_description = a simple erlang worker pool
pkg_worker_pool_homepage = https://github.com/inaka/worker_pool
pkg_worker_pool_fetch = git
pkg_worker_pool_repo = https://github.com/inaka/worker_pool
pkg_worker_pool_commit = 1.0.2
PACKAGES += wrangler
pkg_wrangler_name = wrangler
pkg_wrangler_description = Import of the Wrangler svn repository.
pkg_wrangler_homepage = http://www.cs.kent.ac.uk/projects/wrangler/Home.html
pkg_wrangler_fetch = git
pkg_wrangler_repo = https://github.com/RefactoringTools/wrangler
pkg_wrangler_commit = master
PACKAGES += wsock
pkg_wsock_name = wsock
pkg_wsock_description = Erlang library to build WebSocket clients and servers
pkg_wsock_homepage = https://github.com/madtrick/wsock
pkg_wsock_fetch = git
pkg_wsock_repo = https://github.com/madtrick/wsock
pkg_wsock_commit = master
PACKAGES += xhttpc
pkg_xhttpc_name = xhttpc
pkg_xhttpc_description = Extensible HTTP Client for Erlang
pkg_xhttpc_homepage = https://github.com/seriyps/xhttpc
pkg_xhttpc_fetch = git
pkg_xhttpc_repo = https://github.com/seriyps/xhttpc
pkg_xhttpc_commit = master
PACKAGES += xref_runner
pkg_xref_runner_name = xref_runner
pkg_xref_runner_description = Erlang Xref Runner (inspired in rebar xref)
pkg_xref_runner_homepage = https://github.com/inaka/xref_runner
pkg_xref_runner_fetch = git
pkg_xref_runner_repo = https://github.com/inaka/xref_runner
pkg_xref_runner_commit = 0.2.0
PACKAGES += yamerl
pkg_yamerl_name = yamerl
pkg_yamerl_description = YAML 1.2 parser in pure Erlang
pkg_yamerl_homepage = https://github.com/yakaz/yamerl
pkg_yamerl_fetch = git
pkg_yamerl_repo = https://github.com/yakaz/yamerl
pkg_yamerl_commit = master
PACKAGES += yamler
pkg_yamler_name = yamler
pkg_yamler_description = libyaml-based yaml loader for Erlang
pkg_yamler_homepage = https://github.com/goertzenator/yamler
pkg_yamler_fetch = git
pkg_yamler_repo = https://github.com/goertzenator/yamler
pkg_yamler_commit = master
PACKAGES += yaws
pkg_yaws_name = yaws
pkg_yaws_description = Yaws webserver
pkg_yaws_homepage = http://yaws.hyber.org
pkg_yaws_fetch = git
pkg_yaws_repo = https://github.com/klacke/yaws
pkg_yaws_commit = master
PACKAGES += zab_engine
pkg_zab_engine_name = zab_engine
pkg_zab_engine_description = zab propotocol implement by erlang
pkg_zab_engine_homepage = https://github.com/xinmingyao/zab_engine
pkg_zab_engine_fetch = git
pkg_zab_engine_repo = https://github.com/xinmingyao/zab_engine
pkg_zab_engine_commit = master
PACKAGES += zeta
pkg_zeta_name = zeta
pkg_zeta_description = HTTP access log parser in Erlang
pkg_zeta_homepage = https://github.com/s1n4/zeta
pkg_zeta_fetch = git
pkg_zeta_repo = https://github.com/s1n4/zeta
pkg_zeta_commit =
PACKAGES += zippers
pkg_zippers_name = zippers
pkg_zippers_description = A library for functional zipper data structures in Erlang. Read more on zippers
pkg_zippers_homepage = https://github.com/ferd/zippers
pkg_zippers_fetch = git
pkg_zippers_repo = https://github.com/ferd/zippers
pkg_zippers_commit = master
PACKAGES += zlists
pkg_zlists_name = zlists
pkg_zlists_description = Erlang lazy lists library.
pkg_zlists_homepage = https://github.com/vjache/erlang-zlists
pkg_zlists_fetch = git
pkg_zlists_repo = https://github.com/vjache/erlang-zlists
pkg_zlists_commit = master
PACKAGES += zraft_lib
pkg_zraft_lib_name = zraft_lib
pkg_zraft_lib_description = Erlang raft consensus protocol implementation
pkg_zraft_lib_homepage = https://github.com/dreyk/zraft_lib
pkg_zraft_lib_fetch = git
pkg_zraft_lib_repo = https://github.com/dreyk/zraft_lib
pkg_zraft_lib_commit = master
PACKAGES += zucchini
pkg_zucchini_name = zucchini
pkg_zucchini_description = An Erlang INI parser
pkg_zucchini_homepage = https://github.com/devinus/zucchini
pkg_zucchini_fetch = git
pkg_zucchini_repo = https://github.com/devinus/zucchini
pkg_zucchini_commit = master
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: search
define pkg_print
$(verbose) printf "%s\n" \
$(if $(call core_eq,$(1),$(pkg_$(1)_name)),,"Pkg name: $(1)") \
"App name: $(pkg_$(1)_name)" \
"Description: $(pkg_$(1)_description)" \
"Home page: $(pkg_$(1)_homepage)" \
"Fetch with: $(pkg_$(1)_fetch)" \
"Repository: $(pkg_$(1)_repo)" \
"Commit: $(pkg_$(1)_commit)" \
""
endef
search:
ifdef q
$(foreach p,$(PACKAGES), \
$(if $(findstring $(call core_lc,$(q)),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \
$(call pkg_print,$(p))))
else
$(foreach p,$(PACKAGES),$(call pkg_print,$(p)))
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-deps distclean-pkg
# Configuration.
IGNORE_DEPS ?=
DEPS_DIR ?= $(CURDIR)/deps
export DEPS_DIR
REBAR_DEPS_DIR = $(DEPS_DIR)
export REBAR_DEPS_DIR
ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(filter-out $(IGNORE_DEPS),$(DEPS)))
ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),)
ifeq ($(ERL_LIBS),)
ERL_LIBS = $(DEPS_DIR)
else
ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)
endif
endif
export ERL_LIBS
# Verbosity.
dep_verbose_0 = @echo " DEP " $(1);
dep_verbose = $(dep_verbose_$(V))
# Core targets.
ifneq ($(SKIP_DEPS),)
deps::
else
deps:: $(ALL_DEPS_DIRS)
ifneq ($(IS_DEP),1)
$(verbose) rm -f $(ERLANG_MK_TMP)/deps.log
endif
$(verbose) mkdir -p $(ERLANG_MK_TMP)
$(verbose) for dep in $(ALL_DEPS_DIRS) ; do \
if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \
echo -n; \
else \
echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \
if [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \
$(MAKE) -C $$dep IS_DEP=1 || exit $$?; \
else \
echo "ERROR: No Makefile to build dependency $$dep."; \
exit 1; \
fi \
fi \
done
endif
distclean:: distclean-deps distclean-pkg
# Deps related targets.
# @todo rename GNUmakefile and makefile into Makefile first, if they exist
# While Makefile file could be GNUmakefile or makefile,
# in practice only Makefile is needed so far.
define dep_autopatch
if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
if [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \
$(call dep_autopatch2,$(1)); \
elif [ 0 != `grep -ci rebar $(DEPS_DIR)/$(1)/Makefile` ]; then \
$(call dep_autopatch2,$(1)); \
elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk | xargs -r grep -i rebar`" ]; then \
$(call dep_autopatch2,$(1)); \
else \
if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \
$(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
$(call dep_autopatch_erlang_mk,$(1)); \
else \
$(call erlang,$(call dep_autopatch_app.erl,$(1))); \
fi \
fi \
else \
if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \
$(call dep_autopatch_noop,$(1)); \
else \
$(call dep_autopatch2,$(1)); \
fi \
fi
endef
define dep_autopatch2
$(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
if [ -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script ]; then \
$(call dep_autopatch_fetch_rebar); \
$(call dep_autopatch_rebar,$(1)); \
else \
$(call dep_autopatch_gen,$(1)); \
fi
endef
define dep_autopatch_noop
printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile
endef
# Overwrite erlang.mk with the current file by default.
ifeq ($(NO_AUTOPATCH_ERLANG_MK),)
define dep_autopatch_erlang_mk
echo "include $(ERLANG_MK_FILENAME)" > $(DEPS_DIR)/$(1)/erlang.mk
endef
else
define dep_autopatch_erlang_mk
echo -n
endef
endif
define dep_autopatch_gen
printf "%s\n" \
"ERLC_OPTS = +debug_info" \
"include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile
endef
define dep_autopatch_fetch_rebar
mkdir -p $(ERLANG_MK_TMP); \
if [ ! -d $(ERLANG_MK_TMP)/rebar ]; then \
git clone -q -n -- https://github.com/rebar/rebar $(ERLANG_MK_TMP)/rebar; \
cd $(ERLANG_MK_TMP)/rebar; \
git checkout -q 791db716b5a3a7671e0b351f95ddf24b848ee173; \
$(MAKE); \
cd -; \
fi
endef
define dep_autopatch_rebar
if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \
fi; \
$(call erlang,$(call dep_autopatch_rebar.erl,$(1))); \
rm -f $(DEPS_DIR)/$(1)/ebin/$(1).app
endef
define dep_autopatch_rebar.erl
application:set_env(rebar, log_level, debug),
Conf1 = case file:consult("$(DEPS_DIR)/$(1)/rebar.config") of
{ok, Conf0} -> Conf0;
_ -> []
end,
{Conf, OsEnv} = fun() ->
case filelib:is_file("$(DEPS_DIR)/$(1)/rebar.config.script") of
false -> {Conf1, []};
true ->
Bindings0 = erl_eval:new_bindings(),
Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0),
Bindings = erl_eval:add_binding('SCRIPT', "$(DEPS_DIR)/$(1)/rebar.config.script", Bindings1),
Before = os:getenv(),
{ok, Conf2} = file:script("$(DEPS_DIR)/$(1)/rebar.config.script", Bindings),
{Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)}
end
end(),
Write = fun (Text) ->
file:write_file("$(DEPS_DIR)/$(1)/Makefile", Text, [append])
end,
Escape = fun (Text) ->
re:replace(Text, "\\\\$$$$", "\$$$$$$$$", [global, {return, list}])
end,
Write("IGNORE_DEPS = edown eper eunit_formatters meck node_package "
"rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"),
Write("C_SRC_DIR = /path/do/not/exist\n"),
Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"),
Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]),
fun() ->
Write("ERLC_OPTS = +debug_info\nexport ERLC_OPTS\n"),
case lists:keyfind(erl_opts, 1, Conf) of
false -> ok;
{_, ErlOpts} ->
lists:foreach(fun
({d, D}) ->
Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
({i, I}) ->
Write(["ERLC_OPTS += -I ", I, "\n"]);
({platform_define, Regex, D}) ->
case rebar_utils:is_arch(Regex) of
true -> Write("ERLC_OPTS += -D" ++ atom_to_list(D) ++ "=1\n");
false -> ok
end;
({parse_transform, PT}) ->
Write("ERLC_OPTS += +'{parse_transform, " ++ atom_to_list(PT) ++ "}'\n");
(_) -> ok
end, ErlOpts)
end,
Write("\n")
end(),
fun() ->
File = case lists:keyfind(deps, 1, Conf) of
false -> [];
{_, Deps} ->
[begin case case Dep of
{N, S} when is_atom(N), is_list(S) -> {N, {hex, S}};
{N, S} when is_tuple(S) -> {N, S};
{N, _, S} -> {N, S};
{N, _, S, _} -> {N, S};
_ -> false
end of
false -> ok;
{Name, Source} ->
{Method, Repo, Commit} = case Source of
{hex, V} -> {hex, undefined, V};
{git, R} -> {git, R, master};
{M, R, {branch, C}} -> {M, R, C};
{M, R, {ref, C}} -> {M, R, C};
{M, R, {tag, C}} -> {M, R, C};
{M, R, C} -> {M, R, C}
end,
Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit]))
end end || Dep <- Deps]
end
end(),
fun() ->
case lists:keyfind(erl_first_files, 1, Conf) of
false -> ok;
{_, Files} ->
Names = [[" ", case lists:reverse(F) of
"lre." ++ Elif -> lists:reverse(Elif);
Elif -> lists:reverse(Elif)
end] || "src/" ++ F <- Files],
Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names]))
end
end(),
FindFirst = fun(F, Fd) ->
case io:parse_erl_form(Fd, undefined) of
{ok, {attribute, _, compile, {parse_transform, PT}}, _} ->
[PT, F(F, Fd)];
{ok, {attribute, _, compile, CompileOpts}, _} when is_list(CompileOpts) ->
case proplists:get_value(parse_transform, CompileOpts) of
undefined -> [F(F, Fd)];
PT -> [PT, F(F, Fd)]
end;
{ok, {attribute, _, include, Hrl}, _} ->
case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ ->
case file:open("$(DEPS_DIR)/$(1)/src/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ -> [F(F, Fd)]
end
end;
{ok, {attribute, _, include_lib, "$(1)/include/" ++ Hrl}, _} ->
{ok, HrlFd} = file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]),
[F(F, HrlFd), F(F, Fd)];
{ok, {attribute, _, include_lib, Hrl}, _} ->
case file:open("$(DEPS_DIR)/$(1)/include/" ++ Hrl, [read]) of
{ok, HrlFd} -> [F(F, HrlFd), F(F, Fd)];
_ -> [F(F, Fd)]
end;
{ok, {attribute, _, import, {Imp, _}}, _} ->
case file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(Imp) ++ ".erl", [read]) of
{ok, ImpFd} -> [Imp, F(F, ImpFd), F(F, Fd)];
_ -> [F(F, Fd)]
end;
{eof, _} ->
file:close(Fd),
[];
_ ->
F(F, Fd)
end
end,
fun() ->
ErlFiles = filelib:wildcard("$(DEPS_DIR)/$(1)/src/*.erl"),
First0 = lists:usort(lists:flatten([begin
{ok, Fd} = file:open(F, [read]),
FindFirst(FindFirst, Fd)
end || F <- ErlFiles])),
First = lists:flatten([begin
{ok, Fd} = file:open("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", [read]),
FindFirst(FindFirst, Fd)
end || M <- First0, lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)]) ++ First0,
Write(["COMPILE_FIRST +=", [[" ", atom_to_list(M)] || M <- First,
lists:member("$(DEPS_DIR)/$(1)/src/" ++ atom_to_list(M) ++ ".erl", ErlFiles)], "\n"])
end(),
Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app\n"),
Write("\npreprocess::\n"),
Write("\npre-deps::\n"),
Write("\npre-app::\n"),
PatchHook = fun(Cmd) ->
case Cmd of
"make -C" ++ Cmd1 -> "$$$$\(MAKE) -C" ++ Escape(Cmd1);
"gmake -C" ++ Cmd1 -> "$$$$\(MAKE) -C" ++ Escape(Cmd1);
"make " ++ Cmd1 -> "$$$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
"gmake " ++ Cmd1 -> "$$$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1);
_ -> Escape(Cmd)
end
end,
fun() ->
case lists:keyfind(pre_hooks, 1, Conf) of
false -> ok;
{_, Hooks} ->
[case H of
{'get-deps', Cmd} ->
Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n");
{compile, Cmd} ->
Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
{Regex, compile, Cmd} ->
case rebar_utils:is_arch(Regex) of
true -> Write("\npre-app::\n\tCC=$$$$\(CC) " ++ PatchHook(Cmd) ++ "\n");
false -> ok
end;
_ -> ok
end || H <- Hooks]
end
end(),
ShellToMk = fun(V) ->
re:replace(re:replace(V, "(\\\\$$$$)(\\\\w*)", "\\\\1(\\\\2)", [global]),
"-Werror\\\\b", "", [{return, list}, global])
end,
PortSpecs = fun() ->
case lists:keyfind(port_specs, 1, Conf) of
false ->
case filelib:is_dir("$(DEPS_DIR)/$(1)/c_src") of
false -> [];
true ->
[{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"),
proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}]
end;
{_, Specs} ->
lists:flatten([case S of
{Output, Input} -> {ShellToMk(Output), Input, []};
{Regex, Output, Input} ->
case rebar_utils:is_arch(Regex) of
true -> {ShellToMk(Output), Input, []};
false -> []
end;
{Regex, Output, Input, [{env, Env}]} ->
case rebar_utils:is_arch(Regex) of
true -> {ShellToMk(Output), Input, Env};
false -> []
end
end || S <- Specs])
end
end(),
PortSpecWrite = fun (Text) ->
file:write_file("$(DEPS_DIR)/$(1)/c_src/Makefile.erlang.mk", Text, [append])
end,
case PortSpecs of
[] -> ok;
_ ->
Write("\npre-app::\n\t$$$$\(MAKE) -f c_src/Makefile.erlang.mk\n"),
PortSpecWrite(io_lib:format("ERL_CFLAGS = -finline-functions -Wall -fPIC -I ~s/erts-~s/include -I ~s\n",
[code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])),
PortSpecWrite(io_lib:format("ERL_LDFLAGS = -L ~s -lerl_interface -lei\n",
[code:lib_dir(erl_interface, lib)])),
[PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv],
FilterEnv = fun(Env) ->
lists:flatten([case E of
{_, _} -> E;
{Regex, K, V} ->
case rebar_utils:is_arch(Regex) of
true -> {K, V};
false -> []
end
end || E <- Env])
end,
MergeEnv = fun(Env) ->
lists:foldl(fun ({K, V}, Acc) ->
case lists:keyfind(K, 1, Acc) of
false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc];
{_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc]
end
end, [], Env)
end,
PortEnv = case lists:keyfind(port_env, 1, Conf) of
false -> [];
{_, PortEnv0} -> FilterEnv(PortEnv0)
end,
PortSpec = fun ({Output, Input0, Env}) ->
filelib:ensure_dir("$(DEPS_DIR)/$(1)/" ++ Output),
Input = [[" ", I] || I <- Input0],
PortSpecWrite([
[["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))],
case $(PLATFORM) of
darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress";
_ -> ""
end,
"\n\nall:: ", Output, "\n\n",
"%.o: %.c\n\t$$$$\(CC) -c -o $$$$\@ $$$$\< $$$$\(CFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.C\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.cc\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
"%.o: %.cpp\n\t$$$$\(CXX) -c -o $$$$\@ $$$$\< $$$$\(CXXFLAGS) $$$$\(ERL_CFLAGS) $$$$\(DRV_CFLAGS) $$$$\(EXE_CFLAGS)\n\n",
[[Output, ": ", K, " = ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))],
Output, ": $$$$\(foreach ext,.c .C .cc .cpp,",
"$$$$\(patsubst %$$$$\(ext),%.o,$$$$\(filter %$$$$\(ext),$$$$\(wildcard", Input, "))))\n",
"\t$$$$\(CC) -o $$$$\@ $$$$\? $$$$\(LDFLAGS) $$$$\(ERL_LDFLAGS) $$$$\(DRV_LDFLAGS) $$$$\(EXE_LDFLAGS)",
case filename:extension(Output) of
[] -> "\n";
_ -> " -shared\n"
end])
end,
[PortSpec(S) || S <- PortSpecs]
end,
Write("\ninclude $(ERLANG_MK_FILENAME)"),
RunPlugin = fun(Plugin, Step) ->
case erlang:function_exported(Plugin, Step, 2) of
false -> ok;
true ->
c:cd("$(DEPS_DIR)/$(1)/"),
Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(),
dict:store(base_dir, "", dict:new())}, undefined),
io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret])
end
end,
fun() ->
case lists:keyfind(plugins, 1, Conf) of
false -> ok;
{_, Plugins} ->
[begin
case lists:keyfind(deps, 1, Conf) of
false -> ok;
{_, Deps} ->
case lists:keyfind(P, 1, Deps) of
false -> ok;
_ ->
Path = "$(DEPS_DIR)/" ++ atom_to_list(P),
io:format("~s", [os:cmd("$(MAKE) -C $(DEPS_DIR)/$(1) " ++ Path)]),
io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]),
code:add_patha(Path ++ "/ebin")
end
end
end || P <- Plugins],
[case code:load_file(P) of
{module, P} -> ok;
_ ->
case lists:keyfind(plugin_dir, 1, Conf) of
false -> ok;
{_, PluginsDir} ->
ErlFile = "$(DEPS_DIR)/$(1)/" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl",
{ok, P, Bin} = compile:file(ErlFile, [binary]),
{module, P} = code:load_binary(P, ErlFile, Bin)
end
end || P <- Plugins],
[RunPlugin(P, preprocess) || P <- Plugins],
[RunPlugin(P, pre_compile) || P <- Plugins]
end
end(),
halt()
endef
define dep_autopatch_app.erl
UpdateModules = fun(App) ->
case filelib:is_regular(App) of
false -> ok;
true ->
{ok, [{application, $(1), L0}]} = file:consult(App),
Mods = filelib:fold_files("$(DEPS_DIR)/$(1)/src", "\\\\.erl$$$$", true,
fun (F, Acc) -> [list_to_atom(filename:rootname(filename:basename(F)))|Acc] end, []),
L = lists:keystore(modules, 1, L0, {modules, Mods}),
ok = file:write_file(App, io_lib:format("~p.~n", [{application, $(1), L}]))
end
end,
UpdateModules("$(DEPS_DIR)/$(1)/ebin/$(1).app"),
halt()
endef
define dep_autopatch_appsrc.erl
AppSrcOut = "$(DEPS_DIR)/$(1)/src/$(1).app.src",
AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(DEPS_DIR)/$(1)/ebin/$(1).app"; true -> AppSrcOut end,
case filelib:is_regular(AppSrcIn) of
false -> ok;
true ->
{ok, [{application, $(1), L0}]} = file:consult(AppSrcIn),
L1 = lists:keystore(modules, 1, L0, {modules, []}),
L2 = case lists:keyfind(vsn, 1, L1) of {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, "git"}); _ -> L1 end,
L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end,
ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L3}])),
case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end
end,
halt()
endef
define hex_fetch.erl
ssl:start(),
inets:start(),
{ok, {{_, 200, _}, _, Body}} = httpc:request(get,
{"https://s3.amazonaws.com/s3.hex.pm/tarballs/$(1)-$(2).tar", []},
[], [{body_format, binary}]),
{ok, Files} = erl_tar:extract({binary, Body}, [memory]),
{_, Source} = lists:keyfind("contents.tar.gz", 1, Files),
ok = erl_tar:extract({binary, Source}, [{cwd, "$(DEPS_DIR)/$(1)"}, compressed]),
halt()
endef
define dep_fetch
if [ "$(2)" = "git" ]; then \
git clone -q -n -- $(3) $(DEPS_DIR)/$(1); \
cd $(DEPS_DIR)/$(1) && git checkout -q $(4); \
elif [ "$(2)" = "hg" ]; then \
hg clone -q -U $(3) $(DEPS_DIR)/$(1); \
cd $(DEPS_DIR)/$(1) && hg update -q $(4); \
elif [ "$(2)" = "svn" ]; then \
svn checkout -q $(3) $(DEPS_DIR)/$(1); \
elif [ "$(2)" = "cp" ]; then \
cp -R $(3) $(DEPS_DIR)/$(1); \
elif [ "$(2)" = "hex" ]; then \
$(call erlang,$(call hex_fetch.erl,$(1),$(strip $(4)))); \
else \
echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \
exit 78; \
fi
endef
define dep_target
$(DEPS_DIR)/$(1):
$(verbose) mkdir -p $(DEPS_DIR)
ifeq (,$(dep_$(1)))
$(dep_verbose) $(call dep_fetch,$(pkg_$(1)_name),$(pkg_$(1)_fetch), \
$(patsubst git://github.com/%,https://github.com/%,$(pkg_$(1)_repo)), \
$(pkg_$(1)_commit))
else
ifeq (1,$(words $(dep_$(1))))
$(dep_verbose) $(call dep_fetch,$(1),git, \
$(patsubst git://github.com/%,https://github.com/%,$(dep_$(1))), \
master)
else
ifeq (2,$(words $(dep_$(1))))
$(dep_verbose) $(call dep_fetch,$(1),git, \
$(patsubst git://github.com/%,https://github.com/%,$(word 1,$(dep_$(1)))), \
$(word 2,$(dep_$(1))))
else
$(dep_verbose) $(call dep_fetch,$(1),$(word 1,$(dep_$(1))), \
$(patsubst git://github.com/%,https://github.com/%,$(word 2,$(dep_$(1)))), \
$(word 3,$(dep_$(1))))
endif
endif
endif
$(verbose) if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ]; then \
echo " AUTO " $(1); \
cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \
fi
- $(verbose) if [ -f $(DEPS_DIR)/$(1)/configure ]; then \
echo " CONF " $(1); \
cd $(DEPS_DIR)/$(1) && ./configure; \
fi
ifeq ($(filter $(1),$(NO_AUTOPATCH)),)
$(verbose) if [ "$(1)" = "amqp_client" -a "$(RABBITMQ_CLIENT_PATCH)" ]; then \
if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
echo " PATCH Downloading rabbitmq-codegen"; \
git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
fi; \
if [ ! -d $(DEPS_DIR)/rabbitmq-server ]; then \
echo " PATCH Downloading rabbitmq-server"; \
git clone https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server; \
fi; \
ln -s $(DEPS_DIR)/amqp_client/deps/rabbit_common-0.0.0 $(DEPS_DIR)/rabbit_common; \
elif [ "$(1)" = "rabbit" -a "$(RABBITMQ_SERVER_PATCH)" ]; then \
if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
echo " PATCH Downloading rabbitmq-codegen"; \
git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
fi \
else \
$(call dep_autopatch,$(1)) \
fi
endif
endef
$(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep))))
distclean-deps:
$(gen_verbose) rm -rf $(DEPS_DIR)
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
# Verbosity.
proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F));
proto_verbose = $(proto_verbose_$(V))
# Core targets.
define compile_proto
$(verbose) mkdir -p ebin/ include/
$(proto_verbose) $(call erlang,$(call compile_proto.erl,$(1)))
$(proto_verbose) erlc +debug_info -o ebin/ ebin/*.erl
$(verbose) rm ebin/*.erl
endef
define compile_proto.erl
[begin
Dir = filename:dirname(filename:dirname(F)),
protobuffs_compile:generate_source(F,
[{output_include_dir, Dir ++ "/include"},
{output_src_dir, Dir ++ "/ebin"}])
end || F <- string:tokens("$(1)", " ")],
halt().
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.proto))
$(if $(strip $?),$(call compile_proto,$?))
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: clean-app
# Configuration.
ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
+warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
COMPILE_FIRST ?=
COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
ERLC_EXCLUDE ?=
ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
ERLC_MIB_OPTS ?=
COMPILE_MIB_FIRST ?=
COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
# Verbosity.
app_verbose_0 = @echo " APP " $(PROJECT);
app_verbose = $(app_verbose_$(V))
appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
appsrc_verbose = $(appsrc_verbose_$(V))
erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
$(filter %.erl %.core,$(?F)));
erlc_verbose = $(erlc_verbose_$(V))
xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
xyrl_verbose = $(xyrl_verbose_$(V))
asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F));
asn1_verbose = $(asn1_verbose_$(V))
mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
mib_verbose = $(mib_verbose_$(V))
# Targets.
ifeq ($(wildcard ebin/test),)
app:: app-build
else
app:: clean app-build
endif
ifeq ($(wildcard src/$(PROJECT)_app.erl),)
define app_file
{application, $(PROJECT), [
{description, "$(PROJECT_DESCRIPTION)"},
{vsn, "$(PROJECT_VERSION)"},
{id, "$(1)"},
{modules, [$(call comma_list,$(2))]},
{registered, []},
{applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(DEPS))]}
]}.
endef
else
define app_file
{application, $(PROJECT), [
{description, "$(PROJECT_DESCRIPTION)"},
{vsn, "$(PROJECT_VERSION)"},
{id, "$(1)"},
{modules, [$(call comma_list,$(2))]},
{registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]},
{applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(DEPS))]},
{mod, {$(PROJECT)_app, []}}
]}.
endef
endif
app-build: erlc-include ebin/$(PROJECT).app
$(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null || true))
$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam))))))
ifeq ($(wildcard src/$(PROJECT).app.src),)
$(app_verbose) echo $(subst $(newline),,$(subst ",\",$(call app_file,$(GITDESCRIBE),$(MODULES)))) \
> ebin/$(PROJECT).app
else
$(verbose) if [ -z "$$(grep -E '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \
echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk README for instructions." >&2; \
exit 1; \
fi
$(appsrc_verbose) cat src/$(PROJECT).app.src \
| sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \
| sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(GITDESCRIBE)\"}/" \
> ebin/$(PROJECT).app
endif
erlc-include:
- $(verbose) if [ -d ebin/ ]; then \
find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \
fi
define compile_erl
$(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \
-pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),\
$(COMPILE_FIRST_PATHS) $(1))
endef
define compile_xyrl
$(xyrl_verbose) erlc -v -o ebin/ $(1)
$(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl
$(verbose) rm ebin/*.erl
endef
define compile_asn1
$(asn1_verbose) erlc -v -I include/ -o ebin/ $(1)
$(verbose) mv ebin/*.hrl include/
$(verbose) mv ebin/*.asn1db include/
$(verbose) rm ebin/*.erl
endef
define compile_mib
$(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ \
-I priv/mibs/ $(COMPILE_MIB_FIRST_PATHS) $(1)
$(mib_verbose) erlc -o include/ -- priv/mibs/*.bin
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app::
$(verbose) mkdir -p ebin/
ifneq ($(wildcard asn1/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,asn1/,*.asn1))
$(verbose) mkdir -p include
$(if $(strip $?),$(call compile_asn1,$?))
endif
ifneq ($(wildcard mibs/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,mibs/,*.mib))
$(verbose) mkdir -p priv/mibs/ include
$(if $(strip $?),$(call compile_mib,$?))
endif
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.erl *.core))
$(if $(strip $?),$(call compile_erl,$?))
ebin/$(PROJECT).app:: $(sort $(call core_find,src/,*.xrl *.yrl))
$(if $(strip $?),$(call compile_xyrl,$?))
endif
clean:: clean-app
clean-app:
$(gen_verbose) rm -rf ebin/ priv/mibs/ \
$(addprefix include/,$(addsuffix .hrl,$(notdir $(basename $(call core_find,mibs/,*.mib)))))
# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: docs-deps
# Configuration.
ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))
# Targets.
$(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))
ifneq ($(SKIP_DEPS),)
doc-deps:
else
doc-deps: $(ALL_DOC_DEPS_DIRS)
$(verbose) for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: test-deps test-dir test-build clean-test-dir
# Configuration.
TEST_DIR ?= $(CURDIR)/test
ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
TEST_ERLC_OPTS += -DTEST=1
# Targets.
$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
ifneq ($(SKIP_DEPS),)
test-deps:
else
test-deps: $(ALL_TEST_DEPS_DIRS)
$(verbose) for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done
endif
ifneq ($(wildcard $(TEST_DIR)),)
test-dir:
$(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o $(TEST_DIR) \
$(call core_find,$(TEST_DIR)/,*.erl) -pa ebin/
endif
ifeq ($(wildcard ebin/test),)
test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build:: clean deps test-deps
$(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
$(gen_verbose) touch ebin/test
else
test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build:: deps test-deps
$(verbose) $(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
endif
clean:: clean-test-dir
clean-test-dir:
ifneq ($(wildcard $(TEST_DIR)/*.beam),)
$(gen_verbose) rm -f $(TEST_DIR)/*.beam
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc
MAN_INSTALL_PATH ?= /usr/local/share/man
MAN_SECTIONS ?= 3 7
docs:: asciidoc
asciidoc: distclean-asciidoc doc-deps asciidoc-guide asciidoc-manual
ifeq ($(wildcard doc/src/guide/book.asciidoc),)
asciidoc-guide:
else
asciidoc-guide:
a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf
a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/
endif
ifeq ($(wildcard doc/src/manual/*.asciidoc),)
asciidoc-manual:
else
asciidoc-manual:
for f in doc/src/manual/*.asciidoc ; do \
a2x -v -f manpage $$f ; \
done
for s in $(MAN_SECTIONS); do \
mkdir -p doc/man$$s/ ; \
mv doc/src/manual/*.$$s doc/man$$s/ ; \
gzip doc/man$$s/*.$$s ; \
done
install-docs:: install-asciidoc
install-asciidoc: asciidoc-manual
for s in $(MAN_SECTIONS); do \
mkdir -p $(MAN_INSTALL_PATH)/man$$s/ ; \
install -g 0 -o 0 -m 0644 doc/man$$s/*.gz $(MAN_INSTALL_PATH)/man$$s/ ; \
done
endif
distclean:: distclean-asciidoc
distclean-asciidoc:
$(gen_verbose) rm -rf doc/html/ doc/guide.pdf doc/man3/ doc/man7/
# Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Bootstrap targets:" \
" bootstrap Generate a skeleton of an OTP application" \
" bootstrap-lib Generate a skeleton of an OTP library" \
" bootstrap-rel Generate the files needed to build a release" \
" new t=TPL n=NAME Generate a module NAME based on the template TPL" \
" list-templates List available templates"
# Bootstrap templates.
define bs_appsrc
{application, $(PROJECT), [
{description, ""},
{vsn, "0.1.0"},
{id, "git"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, {$(PROJECT)_app, []}},
{env, []}
]}.
endef
define bs_appsrc_lib
{application, $(PROJECT), [
{description, ""},
{vsn, "0.1.0"},
{id, "git"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]}
]}.
endef
ifdef SP
define bs_Makefile
PROJECT = $(PROJECT)
# Whitespace to be used when creating files from templates.
SP = $(SP)
include erlang.mk
endef
else
define bs_Makefile
PROJECT = $(PROJECT)
include erlang.mk
endef
endif
define bs_app
-module($(PROJECT)_app).
-behaviour(application).
-export([start/2]).
-export([stop/1]).
start(_Type, _Args) ->
$(PROJECT)_sup:start_link().
stop(_State) ->
ok.
endef
define bs_relx_config
{release, {$(PROJECT)_release, "1"}, [$(PROJECT)]}.
{extended_start_script, true}.
{sys_config, "rel/sys.config"}.
{vm_args, "rel/vm.args"}.
endef
define bs_sys_config
[
].
endef
define bs_vm_args
-name $(PROJECT)@127.0.0.1
-setcookie $(PROJECT)
-heart
endef
# Normal templates.
define tpl_supervisor
-module($(n)).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
Procs = [],
{ok, {{one_for_one, 1, 5}, Procs}}.
endef
define tpl_gen_server
-module($(n)).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link(?MODULE, [], []).
%% gen_server.
init([]) ->
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
endef
define tpl_cowboy_http
-module($(n)).
-behaviour(cowboy_http_handler).
-export([init/3]).
-export([handle/2]).
-export([terminate/3]).
-record(state, {
}).
init(_, Req, _Opts) ->
{ok, Req, #state{}}.
handle(Req, State=#state{}) ->
{ok, Req2} = cowboy_req:reply(200, Req),
{ok, Req2, State}.
terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_gen_fsm
-module($(n)).
-behaviour(gen_fsm).
%% API.
-export([start_link/0]).
%% gen_fsm.
-export([init/1]).
-export([state_name/2]).
-export([handle_event/3]).
-export([state_name/3]).
-export([handle_sync_event/4]).
-export([handle_info/3]).
-export([terminate/3]).
-export([code_change/4]).
-record(state, {
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_fsm:start_link(?MODULE, [], []).
%% gen_fsm.
init([]) ->
{ok, state_name, #state{}}.
state_name(_Event, StateData) ->
{next_state, state_name, StateData}.
handle_event(_Event, StateName, StateData) ->
{next_state, StateName, StateData}.
state_name(_Event, _From, StateData) ->
{reply, ignored, state_name, StateData}.
handle_sync_event(_Event, _From, StateName, StateData) ->
{reply, ignored, StateName, StateData}.
handle_info(_Info, StateName, StateData) ->
{next_state, StateName, StateData}.
terminate(_Reason, _StateName, _StateData) ->
ok.
code_change(_OldVsn, StateName, StateData, _Extra) ->
{ok, StateName, StateData}.
endef
define tpl_cowboy_loop
-module($(n)).
-behaviour(cowboy_loop_handler).
-export([init/3]).
-export([info/3]).
-export([terminate/3]).
-record(state, {
}).
init(_, Req, _Opts) ->
{loop, Req, #state{}, 5000, hibernate}.
info(_Info, Req, State) ->
{loop, Req, State, hibernate}.
terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_cowboy_rest
-module($(n)).
-export([init/3]).
-export([content_types_provided/2]).
-export([get_html/2]).
init(_, _Req, _Opts) ->
{upgrade, protocol, cowboy_rest}.
content_types_provided(Req, State) ->
{[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}.
get_html(Req, State) ->
{<<"<html><body>This is REST!</body></html>">>, Req, State}.
endef
define tpl_cowboy_ws
-module($(n)).
-behaviour(cowboy_websocket_handler).
-export([init/3]).
-export([websocket_init/3]).
-export([websocket_handle/3]).
-export([websocket_info/3]).
-export([websocket_terminate/3]).
-record(state, {
}).
init(_, _, _) ->
{upgrade, protocol, cowboy_websocket}.
websocket_init(_, Req, _Opts) ->
Req2 = cowboy_req:compact(Req),
{ok, Req2, #state{}}.
websocket_handle({text, Data}, Req, State) ->
{reply, {text, Data}, Req, State};
websocket_handle({binary, Data}, Req, State) ->
{reply, {binary, Data}, Req, State};
websocket_handle(_Frame, Req, State) ->
{ok, Req, State}.
websocket_info(_Info, Req, State) ->
{ok, Req, State}.
websocket_terminate(_Reason, _Req, _State) ->
ok.
endef
define tpl_ranch_protocol
-module($(n)).
-behaviour(ranch_protocol).
-export([start_link/4]).
-export([init/4]).
-type opts() :: [].
-export_type([opts/0]).
-record(state, {
socket :: inet:socket(),
transport :: module()
}).
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.
-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, _Opts) ->
ok = ranch:accept_ack(Ref),
loop(#state{socket=Socket, transport=Transport}).
loop(State) ->
loop(State).
endef
# Plugin-specific targets.
define render_template
$(verbose) echo "$${_$(1)}" > $(2)
endef
ifndef WS
ifdef SP
WS = $(subst a,,a $(wordlist 1,$(SP),a a a a a a a a a a a a a a a a a a a a))
else
WS = $(tab)
endif
endif
$(foreach template,$(filter bs_% tpl_%,$(.VARIABLES)), \
$(eval _$(template) = $$(subst $$(tab),$$(WS),$$($(template)))) \
$(eval export _$(template)))
bootstrap:
ifneq ($(wildcard src/),)
$(error Error: src/ directory already exists)
endif
$(call render_template,bs_Makefile,Makefile)
$(verbose) mkdir src/
$(call render_template,bs_appsrc,src/$(PROJECT).app.src)
$(call render_template,bs_app,src/$(PROJECT)_app.erl)
$(eval n := $(PROJECT)_sup)
$(call render_template,tpl_supervisor,src/$(PROJECT)_sup.erl)
bootstrap-lib:
ifneq ($(wildcard src/),)
$(error Error: src/ directory already exists)
endif
$(call render_template,bs_Makefile,Makefile)
$(verbose) mkdir src/
$(call render_template,bs_appsrc_lib,src/$(PROJECT).app.src)
bootstrap-rel:
ifneq ($(wildcard relx.config),)
$(error Error: relx.config already exists)
endif
ifneq ($(wildcard rel/),)
$(error Error: rel/ directory already exists)
endif
$(call render_template,bs_relx_config,relx.config)
$(verbose) mkdir rel/
$(call render_template,bs_sys_config,rel/sys.config)
$(call render_template,bs_vm_args,rel/vm.args)
new:
ifeq ($(wildcard src/),)
$(error Error: src/ directory does not exist)
endif
ifndef t
$(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
endif
ifndef tpl_$(t)
$(error Unknown template)
endif
ifndef n
$(error Usage: $(MAKE) new t=TEMPLATE n=NAME)
endif
$(call render_template,tpl_$(t),src/$(n).erl)
list-templates:
$(verbose) echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))
# Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: clean-c_src distclean-c_src-env
# Configuration.
C_SRC_DIR ?= $(CURDIR)/c_src
C_SRC_ENV ?= $(C_SRC_DIR)/env.mk
C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT).so
C_SRC_TYPE ?= shared
# System type and C compiler/flags.
ifeq ($(PLATFORM),darwin)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall
LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress
else ifeq ($(PLATFORM),freebsd)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
else ifeq ($(PLATFORM),linux)
CC ?= gcc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
endif
CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei
ifeq ($(C_SRC_TYPE),shared)
LDFLAGS += -shared
endif
# Verbosity.
c_verbose_0 = @echo " C " $(?F);
c_verbose = $(c_verbose_$(V))
cpp_verbose_0 = @echo " CPP " $(?F);
cpp_verbose = $(cpp_verbose_$(V))
link_verbose_0 = @echo " LD " $(@F);
link_verbose = $(link_verbose_$(V))
# Targets.
ifeq ($(wildcard $(C_SRC_DIR)),)
else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),)
app:: app-c_src
test-build:: app-c_src
app-c_src:
$(MAKE) -C $(C_SRC_DIR)
clean::
$(MAKE) -C $(C_SRC_DIR) clean
else
ifeq ($(SOURCES),)
SOURCES := $(sort $(call core_find,$(C_SRC_DIR)/,*.c *.C *.cc *.cpp))
endif
OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
app:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
$(C_SRC_OUTPUT): $(OBJECTS)
$(verbose) mkdir -p priv/
$(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT)
%.o: %.c
$(COMPILE_C) $(OUTPUT_OPTION) $<
%.o: %.cc
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.C
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.cpp
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
clean:: clean-c_src
clean-c_src:
$(gen_verbose) rm -f $(C_SRC_OUTPUT) $(OBJECTS)
endif
ifneq ($(wildcard $(C_SRC_DIR)),)
$(C_SRC_ENV):
$(verbose) $(ERL) -eval "file:write_file(\"$(C_SRC_ENV)\", \
io_lib:format( \
\"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \
\"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \
\"ERL_INTERFACE_LIB_DIR ?= ~s~n\", \
[code:root_dir(), erlang:system_info(version), \
code:lib_dir(erl_interface, include), \
code:lib_dir(erl_interface, lib)])), \
halt()."
distclean:: distclean-c_src-env
distclean-c_src-env:
$(gen_verbose) rm -f $(C_SRC_ENV)
-include $(C_SRC_ENV)
endif
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: ci ci-setup distclean-kerl
KERL ?= $(CURDIR)/kerl
export KERL
KERL_URL ?= https://raw.githubusercontent.com/yrashk/kerl/master/kerl
OTP_GIT ?= https://github.com/erlang/otp
CI_INSTALL_DIR ?= $(HOME)/erlang
CI_OTP ?=
ifeq ($(strip $(CI_OTP)),)
ci::
else
ci:: $(addprefix ci-,$(CI_OTP))
ci-prepare: $(addprefix $(CI_INSTALL_DIR)/,$(CI_OTP))
ci-setup::
ci_verbose_0 = @echo " CI " $(1);
ci_verbose = $(ci_verbose_$(V))
define ci_target
ci-$(1): $(CI_INSTALL_DIR)/$(1)
$(ci_verbose) \
PATH="$(CI_INSTALL_DIR)/$(1)/bin:$(PATH)" \
CI_OTP_RELEASE="$(1)" \
CT_OPTS="-label $(1)" \
$(MAKE) clean ci-setup tests
endef
$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp))))
define ci_otp_target
ifeq ($(wildcard $(CI_INSTALL_DIR)/$(1)),)
$(CI_INSTALL_DIR)/$(1): $(KERL)
$(KERL) build git $(OTP_GIT) $(1) $(1)
$(KERL) install $(1) $(CI_INSTALL_DIR)/$(1)
endif
endef
$(foreach otp,$(CI_OTP),$(eval $(call ci_otp_target,$(otp))))
$(KERL):
$(gen_verbose) $(call core_http_get,$(KERL),$(KERL_URL))
$(verbose) chmod +x $(KERL)
help::
$(verbose) printf "%s\n" "" \
"Continuous Integration targets:" \
" ci Run '$(MAKE) tests' on all configured Erlang versions." \
"" \
"The CI_OTP variable must be defined with the Erlang versions" \
"that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3"
distclean:: distclean-kerl
distclean-kerl:
$(gen_verbose) rm -rf $(KERL)
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: ct distclean-ct
# Configuration.
CT_OPTS ?=
ifneq ($(wildcard $(TEST_DIR)),)
CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl))))
else
CT_SUITES ?=
endif
# Core targets.
tests:: ct
distclean:: distclean-ct
help::
$(verbose) printf "%s\n" "" \
"Common_test targets:" \
" ct Run all the common_test suites for this project" \
"" \
"All your common_test suites have their associated targets." \
"A suite named http_SUITE can be ran using the ct-http target."
# Plugin-specific targets.
CT_RUN = ct_run \
-no_auto_compile \
-noinput \
-pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin $(TEST_DIR) \
-dir $(TEST_DIR) \
-logdir $(CURDIR)/logs
ifeq ($(CT_SUITES),)
ct:
else
ct: test-build
$(verbose) mkdir -p $(CURDIR)/logs/
$(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS)
endif
define ct_suite_target
ct-$(1): test-build
$(verbose) mkdir -p $(CURDIR)/logs/
$(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS)
endef
$(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test))))
distclean-ct:
$(gen_verbose) rm -rf $(CURDIR)/logs/
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: plt distclean-plt dialyze
# Configuration.
DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt
export DIALYZER_PLT
PLT_APPS ?=
DIALYZER_DIRS ?= --src -r src
DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \
-Wunmatched_returns # -Wunderspecs
# Core targets.
check:: dialyze
distclean:: distclean-plt
help::
$(verbose) printf "%s\n" "" \
"Dialyzer targets:" \
" plt Build a PLT file for this project" \
" dialyze Analyze the project using Dialyzer"
# Plugin-specific targets.
$(DIALYZER_PLT): deps app
$(verbose) dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(ALL_DEPS_DIRS)
plt: $(DIALYZER_PLT)
distclean-plt:
$(gen_verbose) rm -f $(DIALYZER_PLT)
ifneq ($(wildcard $(DIALYZER_PLT)),)
dialyze:
else
dialyze: $(DIALYZER_PLT)
endif
$(verbose) dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-edoc edoc
# Configuration.
EDOC_OPTS ?=
# Core targets.
docs:: distclean-edoc edoc
distclean:: distclean-edoc
# Plugin-specific targets.
edoc: doc-deps
$(gen_verbose) $(ERL) -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), halt().'
distclean-edoc:
$(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
# Copyright (c) 2015, Erlang Solutions Ltd.
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: elvis distclean-elvis
# Configuration.
ELVIS_CONFIG ?= $(CURDIR)/elvis.config
ELVIS ?= $(CURDIR)/elvis
export ELVIS
ELVIS_URL ?= https://github.com/inaka/elvis/releases/download/0.2.5-beta2/elvis
ELVIS_CONFIG_URL ?= https://github.com/inaka/elvis/releases/download/0.2.5-beta2/elvis.config
ELVIS_OPTS ?=
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Elvis targets:" \
" elvis Run Elvis using the local elvis.config or download the default otherwise"
distclean:: distclean-elvis
# Plugin-specific targets.
$(ELVIS):
$(gen_verbose) $(call core_http_get,$(ELVIS),$(ELVIS_URL))
$(verbose) chmod +x $(ELVIS)
$(ELVIS_CONFIG):
$(verbose) $(call core_http_get,$(ELVIS_CONFIG),$(ELVIS_CONFIG_URL))
elvis: $(ELVIS) $(ELVIS_CONFIG)
$(verbose) $(ELVIS) rock -c $(ELVIS_CONFIG) $(ELVIS_OPTS)
distclean-elvis:
$(gen_verbose) rm -rf $(ELVIS)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
# Configuration.
DTL_FULL_PATH ?= 0
# Verbosity.
dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
dtl_verbose = $(dtl_verbose_$(V))
# Core targets.
define compile_erlydtl
$(dtl_verbose) $(ERL) -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \
Compile = fun(F) -> \
S = fun (1) -> re:replace(filename:rootname(string:sub_string(F, 11), ".dtl"), "/", "_", [{return, list}, global]); \
(0) -> filename:basename(F, ".dtl") \
end, \
Module = list_to_atom(string:to_lower(S($(DTL_FULL_PATH))) ++ "_dtl"), \
{ok, _} = erlydtl:compile(F, Module, [{out_dir, "ebin/"}, return_errors, {doc_root, "templates"}]) \
end, \
_ = [Compile(F) || F <- string:tokens("$(1)", " ")], \
halt().'
endef
ifneq ($(wildcard src/),)
ebin/$(PROJECT).app:: $(sort $(call core_find,templates/,*.dtl))
$(if $(strip $?),$(call compile_erlydtl,$?))
endif
# Copyright (c) 2014 Dave Cottlehuber <dch@skunkwerks.at>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-escript escript
# Configuration.
ESCRIPT_NAME ?= $(PROJECT)
ESCRIPT_COMMENT ?= This is an -*- erlang -*- file
ESCRIPT_BEAMS ?= "ebin/*", "deps/*/ebin/*"
ESCRIPT_SYS_CONFIG ?= "rel/sys.config"
ESCRIPT_EMU_ARGS ?= -pa . \
-sasl errlog_type error \
-escript main $(ESCRIPT_NAME)
ESCRIPT_SHEBANG ?= /usr/bin/env escript
ESCRIPT_STATIC ?= "deps/*/priv/**", "priv/**"
# Core targets.
distclean:: distclean-escript
help::
$(verbose) printf "%s\n" "" \
"Escript targets:" \
" escript Build an executable escript archive" \
# Plugin-specific targets.
# Based on https://github.com/synrc/mad/blob/master/src/mad_bundle.erl
# Copyright (c) 2013 Maxim Sokhatsky, Synrc Research Center
# Modified MIT License, https://github.com/synrc/mad/blob/master/LICENSE :
# Software may only be used for the great good and the true happiness of all
# sentient beings.
define ESCRIPT_RAW
'Read = fun(F) -> {ok, B} = file:read_file(filename:absname(F)), B end,'\
'Files = fun(L) -> A = lists:concat([filelib:wildcard(X)||X<- L ]),'\
' [F || F <- A, not filelib:is_dir(F) ] end,'\
'Squash = fun(L) -> [{filename:basename(F), Read(F) } || F <- L ] end,'\
'Zip = fun(A, L) -> {ok,{_,Z}} = zip:create(A, L, [{compress,all},memory]), Z end,'\
'Ez = fun(Escript) ->'\
' Static = Files([$(ESCRIPT_STATIC)]),'\
' Beams = Squash(Files([$(ESCRIPT_BEAMS), $(ESCRIPT_SYS_CONFIG)])),'\
' Archive = Beams ++ [{ "static.gz", Zip("static.gz", Static)}],'\
' escript:create(Escript, [ $(ESCRIPT_OPTIONS)'\
' {archive, Archive, [memory]},'\
' {shebang, "$(ESCRIPT_SHEBANG)"},'\
' {comment, "$(ESCRIPT_COMMENT)"},'\
' {emu_args, " $(ESCRIPT_EMU_ARGS)"}'\
' ]),'\
' file:change_mode(Escript, 8#755)'\
'end,'\
'Ez("$(ESCRIPT_NAME)"),'\
'halt().'
endef
ESCRIPT_COMMAND = $(subst ' ',,$(ESCRIPT_RAW))
escript:: distclean-escript deps app
$(gen_verbose) $(ERL) -eval $(ESCRIPT_COMMAND)
distclean-escript:
$(gen_verbose) rm -f $(ESCRIPT_NAME)
# Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is contributed to erlang.mk and subject to the terms of the ISC License.
.PHONY: eunit
# Configuration
EUNIT_OPTS ?=
# Core targets.
tests:: eunit
help::
$(verbose) printf "%s\n" "" \
"EUnit targets:" \
" eunit Run all the EUnit tests for this project"
# Plugin-specific targets.
define eunit.erl
case "$(COVER)" of
"" -> ok;
_ ->
case cover:compile_beam_directory("ebin") of
{error, _} -> halt(1);
_ -> ok
end
end,
case eunit:test([$(call comma_list,$(1))], [$(EUNIT_OPTS)]) of
ok -> ok;
error -> halt(2)
end,
case "$(COVER)" of
"" -> ok;
_ ->
cover:export("eunit.coverdata")
end,
halt()
endef
EUNIT_EBIN_MODS = $(notdir $(basename $(call core_find,ebin/,*.beam)))
EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.beam)))
EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \
$(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),{module,'$(mod)'})
eunit: test-build
$(gen_verbose) $(ERL) -pa $(TEST_DIR) $(DEPS_DIR)/*/ebin ebin \
-eval "$(subst $(newline),,$(subst ",\",$(call eunit.erl,$(EUNIT_MODS))))"
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: relx-rel distclean-relx-rel distclean-relx run
# Configuration.
RELX_CONFIG ?= $(CURDIR)/relx.config
RELX ?= $(CURDIR)/relx
export RELX
RELX_URL ?= https://github.com/erlware/relx/releases/download/v2.0.0/relx
RELX_OPTS ?=
RELX_OUTPUT_DIR ?= _rel
ifeq ($(firstword $(RELX_OPTS)),-o)
RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS))
else
RELX_OPTS += -o $(RELX_OUTPUT_DIR)
endif
# Core targets.
ifeq ($(IS_DEP),)
ifneq ($(wildcard $(RELX_CONFIG)),)
rel:: distclean-relx-rel relx-rel
endif
endif
distclean:: distclean-relx-rel distclean-relx
# Plugin-specific targets.
$(RELX):
$(gen_verbose) $(call core_http_get,$(RELX),$(RELX_URL))
$(verbose) chmod +x $(RELX)
relx-rel: $(RELX)
$(verbose) $(RELX) -c $(RELX_CONFIG) $(RELX_OPTS)
distclean-relx-rel:
$(gen_verbose) rm -rf $(RELX_OUTPUT_DIR)
distclean-relx:
$(gen_verbose) rm -rf $(RELX)
# Run target.
ifeq ($(wildcard $(RELX_CONFIG)),)
run:
else
define get_relx_release.erl
{ok, Config} = file:consult("$(RELX_CONFIG)"),
{release, {Name, _}, _} = lists:keyfind(release, 1, Config),
io:format("~s", [Name]),
halt(0).
endef
RELX_RELEASE = `$(call erlang,$(get_relx_release.erl))`
run: all
$(verbose) $(RELX_OUTPUT_DIR)/$(RELX_RELEASE)/bin/$(RELX_RELEASE) console
help::
$(verbose) printf "%s\n" "" \
"Relx targets:" \
" run Compile the project, build the release and run it"
endif
# Copyright (c) 2014, M Robert Martin <rob@version2beta.com>
# This file is contributed to erlang.mk and subject to the terms of the ISC License.
.PHONY: shell
# Configuration.
SHELL_PATH ?= -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin
SHELL_OPTS ?=
ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))
# Core targets
help::
$(verbose) printf "%s\n" "" \
"Shell targets:" \
" shell Run an erlang shell with SHELL_OPTS or reasonable default"
# Plugin-specific targets.
$(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep))))
build-shell-deps: $(ALL_SHELL_DEPS_DIRS)
$(verbose) for dep in $(ALL_SHELL_DEPS_DIRS) ; do $(MAKE) -C $$dep ; done
shell: build-shell-deps
$(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS)
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
ifneq ($(wildcard $(DEPS_DIR)/triq),)
.PHONY: triq
# Targets.
tests:: triq
define triq_check.erl
code:add_pathsa(["$(CURDIR)/ebin", "$(DEPS_DIR)/*/ebin"]),
try
case $(1) of
all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]);
module -> triq:check($(2));
function -> triq:check($(2))
end
of
true -> halt(0);
_ -> halt(1)
catch error:undef ->
io:format("Undefined property or module~n"),
halt(0)
end.
endef
ifdef t
ifeq (,$(findstring :,$(t)))
triq: test-build
$(verbose) $(call erlang,$(call triq_check.erl,module,$(t)))
else
triq: test-build
$(verbose) echo Testing $(t)/0
$(verbose) $(call erlang,$(call triq_check.erl,function,$(t)()))
endif
else
triq: test-build
$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename $(wildcard ebin/*.beam))))))
$(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES)))
endif
endif
# Copyright (c) 2015, Erlang Solutions Ltd.
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: xref distclean-xref
# Configuration.
ifeq ($(XREF_CONFIG),)
XREF_ARGS :=
else
XREF_ARGS := -c $(XREF_CONFIG)
endif
XREFR ?= $(CURDIR)/xrefr
export XREFR
XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/0.2.2/xrefr
# Core targets.
help::
$(verbose) printf "%s\n" "" \
"Xref targets:" \
" xref Run Xrefr using $XREF_CONFIG as config file if defined"
distclean:: distclean-xref
# Plugin-specific targets.
$(XREFR):
$(gen_verbose) $(call core_http_get,$(XREFR),$(XREFR_URL))
$(verbose) chmod +x $(XREFR)
xref: deps app $(XREFR)
$(gen_verbose) $(XREFR) $(XREFR_ARGS)
distclean-xref:
$(gen_verbose) rm -rf $(XREFR)
# Copyright 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
# This file is part of erlang.mk and subject to the terms of the ISC License.
COVER_REPORT_DIR = cover
# Hook in coverage to ct
ifdef COVER
ifdef CT_RUN
# All modules in 'ebin'
COVER_MODS = $(notdir $(basename $(call core_ls,ebin/*.beam)))
test-build:: $(TEST_DIR)/ct.cover.spec
$(TEST_DIR)/ct.cover.spec:
$(verbose) echo Cover mods: $(COVER_MODS)
$(gen_verbose) printf "%s\n" \
'{incl_mods,[$(subst $(space),$(comma),$(COVER_MODS))]}.' \
'{export,"$(CURDIR)/ct.coverdata"}.' > $@
CT_RUN += -cover $(TEST_DIR)/ct.cover.spec
endif
endif
# Core targets
ifdef COVER
ifneq ($(COVER_REPORT_DIR),)
tests::
$(verbose) $(MAKE) --no-print-directory cover-report
endif
endif
clean:: coverdata-clean
ifneq ($(COVER_REPORT_DIR),)
distclean:: cover-report-clean
endif
help::
$(verbose) printf "%s\n" "" \
"Cover targets:" \
" cover-report Generate a HTML coverage report from previously collected" \
" cover data." \
" all.coverdata Merge {eunit,ct}.coverdata into one coverdata file." \
"" \
"If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \
"target tests additionally generates a HTML coverage report from the combined" \
"coverdata files from each of these testing tools. HTML reports can be disabled" \
"by setting COVER_REPORT_DIR to empty."
# Plugin specific targets
COVERDATA = $(filter-out all.coverdata,$(wildcard *.coverdata))
.PHONY: coverdata-clean
coverdata-clean:
$(gen_verbose) rm -f *.coverdata ct.cover.spec
# Merge all coverdata files into one.
all.coverdata: $(COVERDATA)
$(gen_verbose) $(ERL) -eval ' \
$(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) \
cover:export("$@"), halt(0).'
# These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to
# empty if you want the coverdata files but not the HTML report.
ifneq ($(COVER_REPORT_DIR),)
.PHONY: cover-report-clean cover-report
cover-report-clean:
$(gen_verbose) rm -rf $(COVER_REPORT_DIR)
ifeq ($(COVERDATA),)
cover-report:
else
# Modules which include eunit.hrl always contain one line without coverage
# because eunit defines test/0 which is never called. We compensate for this.
EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \
grep -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \
| sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq))
define cover_report.erl
$(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
Ms = cover:imported_modules(),
[cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M)
++ ".COVER.html", [html]) || M <- Ms],
Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],
EunitHrlMods = [$(EUNIT_HRL_MODS)],
Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of
true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],
TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),
TotalN = lists:sum([N || {_, {_, N}} <- Report1]),
TotalPerc = round(100 * TotalY / (TotalY + TotalN)),
{ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]),
io:format(F, "<!DOCTYPE html><html>~n"
"<head><meta charset=\"UTF-8\">~n"
"<title>Coverage report</title></head>~n"
"<body>~n", []),
io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
[io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
"<td>~p%</td></tr>~n",
[M, M, round(100 * Y / (Y + N))]) || {M, {Y, N}} <- Report1],
How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))",
Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")",
io:format(F, "</table>~n"
"<p>Generated using ~s and erlang.mk on ~s.</p>~n"
"</body></html>", [How, Date]),
halt().
endef
cover-report:
$(gen_verbose) mkdir -p $(COVER_REPORT_DIR)
$(gen_verbose) $(call erlang,$(cover_report.erl))
endif
endif # ifneq ($(COVER_REPORT_DIR),)
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
{application, ranch, [
{description, "Socket acceptor pool for TCP protocols."},
{vsn, "1.2.0"},
{id, "git"},
{modules, []},
{registered, [ranch_sup, ranch_server]},
{applications, [
kernel,
stdlib
]},
{mod, {ranch_app, []}},
{env, []}
]}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch).
-export([start_listener/6]).
-export([stop_listener/1]).
-export([child_spec/6]).
-export([accept_ack/1]).
-export([remove_connection/1]).
-export([get_addr/1]).
-export([get_port/1]).
-export([get_max_connections/1]).
-export([set_max_connections/2]).
-export([get_protocol_options/1]).
-export([set_protocol_options/2]).
-export([filter_options/3]).
-export([set_option_default/3]).
-export([require/1]).
-type max_conns() :: non_neg_integer() | infinity.
-export_type([max_conns/0]).
-type opt() :: {ack_timeout, timeout()}
| {connection_type, worker | supervisor}
| {max_connections, max_conns()}
| {shutdown, timeout() | brutal_kill}
| {socket, any()}.
-export_type([opt/0]).
-type ref() :: any().
-export_type([ref/0]).
-spec start_listener(ref(), non_neg_integer(), module(), any(), module(), any())
-> supervisor:startchild_ret().
start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
when is_integer(NbAcceptors) andalso is_atom(Transport)
andalso is_atom(Protocol) ->
_ = code:ensure_loaded(Transport),
case erlang:function_exported(Transport, name, 0) of
false ->
{error, badarg};
true ->
Res = supervisor:start_child(ranch_sup, child_spec(Ref, NbAcceptors,
Transport, TransOpts, Protocol, ProtoOpts)),
Socket = proplists:get_value(socket, TransOpts),
case Res of
{ok, Pid} when Socket =/= undefined ->
%% Give ownership of the socket to ranch_acceptors_sup
%% to make sure the socket stays open as long as the
%% listener is alive. If the socket closes however there
%% will be no way to recover because we don't know how
%% to open it again.
Children = supervisor:which_children(Pid),
{_, AcceptorsSup, _, _}
= lists:keyfind(ranch_acceptors_sup, 1, Children),
%%% Note: the catch is here because SSL crashes when you change
%%% the controlling process of a listen socket because of a bug.
%%% The bug will be fixed in R16.
catch Transport:controlling_process(Socket, AcceptorsSup);
_ ->
ok
end,
Res
end.
-spec stop_listener(ref()) -> ok | {error, not_found}.
stop_listener(Ref) ->
case supervisor:terminate_child(ranch_sup, {ranch_listener_sup, Ref}) of
ok ->
_ = supervisor:delete_child(ranch_sup, {ranch_listener_sup, Ref}),
ranch_server:cleanup_listener_opts(Ref);
{error, Reason} ->
{error, Reason}
end.
-spec child_spec(ref(), non_neg_integer(), module(), any(), module(), any())
-> supervisor:child_spec().
child_spec(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
when is_integer(NbAcceptors) andalso is_atom(Transport)
andalso is_atom(Protocol) ->
{{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [
Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts
]}, permanent, infinity, supervisor, [ranch_listener_sup]}.
-spec accept_ack(ref()) -> ok.
accept_ack(Ref) ->
receive {shoot, Ref, Transport, Socket, AckTimeout} ->
Transport:accept_ack(Socket, AckTimeout)
end.
-spec remove_connection(ref()) -> ok.
remove_connection(Ref) ->
ConnsSup = ranch_server:get_connections_sup(Ref),
ConnsSup ! {remove_connection, Ref},
ok.
-spec get_addr(ref()) -> {inet:ip_address(), inet:port_number()}.
get_addr(Ref) ->
ranch_server:get_addr(Ref).
-spec get_port(ref()) -> inet:port_number().
get_port(Ref) ->
{_, Port} = get_addr(Ref),
Port.
-spec get_max_connections(ref()) -> max_conns().
get_max_connections(Ref) ->
ranch_server:get_max_connections(Ref).
-spec set_max_connections(ref(), max_conns()) -> ok.
set_max_connections(Ref, MaxConnections) ->
ranch_server:set_max_connections(Ref, MaxConnections).
-spec get_protocol_options(ref()) -> any().
get_protocol_options(Ref) ->
ranch_server:get_protocol_options(Ref).
-spec set_protocol_options(ref(), any()) -> ok.
set_protocol_options(Ref, Opts) ->
ranch_server:set_protocol_options(Ref, Opts).
-spec filter_options([inet | inet6 | {atom(), any()} | {raw, any(), any(), any()}],
[atom()], Acc) -> Acc when Acc :: [any()].
filter_options(UserOptions, AllowedKeys, DefaultOptions) ->
AllowedOptions = filter_user_options(UserOptions, AllowedKeys),
lists:foldl(fun merge_options/2, DefaultOptions, AllowedOptions).
%% 2-tuple options.
filter_user_options([Opt = {Key, _}|Tail], AllowedKeys) ->
case lists:member(Key, AllowedKeys) of
true ->
[Opt|filter_user_options(Tail, AllowedKeys)];
false ->
filter_options_warning(Opt),
filter_user_options(Tail, AllowedKeys)
end;
%% Special option forms.
filter_user_options([inet|Tail], AllowedKeys) ->
[inet|filter_user_options(Tail, AllowedKeys)];
filter_user_options([inet6|Tail], AllowedKeys) ->
[inet6|filter_user_options(Tail, AllowedKeys)];
filter_user_options([Opt = {raw, _, _, _}|Tail], AllowedKeys) ->
[Opt|filter_user_options(Tail, AllowedKeys)];
filter_user_options([Opt|Tail], AllowedKeys) ->
filter_options_warning(Opt),
filter_user_options(Tail, AllowedKeys);
filter_user_options([], _) ->
[].
filter_options_warning(Opt) ->
error_logger:warning_msg("Transport option ~p unknown or invalid.~n", [Opt]).
merge_options({Key, _} = Option, OptionList) ->
lists:keystore(Key, 1, OptionList, Option);
merge_options(Option, OptionList) ->
[Option|OptionList].
-spec set_option_default(Opts, atom(), any())
-> Opts when Opts :: [{atom(), any()}].
set_option_default(Opts, Key, Value) ->
case lists:keymember(Key, 1, Opts) of
true -> Opts;
false -> [{Key, Value}|Opts]
end.
-spec require([atom()]) -> ok.
require([]) ->
ok;
require([App|Tail]) ->
case application:start(App) of
ok -> ok;
{error, {already_started, App}} -> ok
end,
require(Tail).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_acceptor).
-export([start_link/3]).
-export([loop/3]).
-spec start_link(inet:socket(), module(), pid())
-> {ok, pid()}.
start_link(LSocket, Transport, ConnsSup) ->
Pid = spawn_link(?MODULE, loop, [LSocket, Transport, ConnsSup]),
{ok, Pid}.
-spec loop(inet:socket(), module(), pid()) -> no_return().
loop(LSocket, Transport, ConnsSup) ->
_ = case Transport:accept(LSocket, infinity) of
{ok, CSocket} ->
case Transport:controlling_process(CSocket, ConnsSup) of
ok ->
%% This call will not return until process has been started
%% AND we are below the maximum number of connections.
ranch_conns_sup:start_protocol(ConnsSup, CSocket);
{error, _} ->
Transport:close(CSocket)
end;
%% Reduce the accept rate if we run out of file descriptors.
%% We can't accept anymore anyway, so we might as well wait
%% a little for the situation to resolve itself.
{error, emfile} ->
receive after 100 -> ok end;
%% We want to crash if the listening socket got closed.
{error, Reason} when Reason =/= closed ->
ok
end,
flush(),
?MODULE:loop(LSocket, Transport, ConnsSup).
flush() ->
receive Msg ->
error_logger:error_msg(
"Ranch acceptor received unexpected message: ~p~n",
[Msg]),
flush()
after 0 ->
ok
end.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_acceptors_sup).
-behaviour(supervisor).
-export([start_link/4]).
-export([init/1]).
-spec start_link(ranch:ref(), non_neg_integer(), module(), any())
-> {ok, pid()}.
start_link(Ref, NbAcceptors, Transport, TransOpts) ->
supervisor:start_link(?MODULE, [Ref, NbAcceptors, Transport, TransOpts]).
init([Ref, NbAcceptors, Transport, TransOpts]) ->
ConnsSup = ranch_server:get_connections_sup(Ref),
LSocket = case proplists:get_value(socket, TransOpts) of
undefined ->
TransOpts2 = proplists:delete(ack_timeout,
proplists:delete(connection_type,
proplists:delete(max_connections,
proplists:delete(shutdown,
proplists:delete(socket, TransOpts))))),
case Transport:listen(TransOpts2) of
{ok, Socket} -> Socket;
{error, Reason} -> listen_error(Ref, Transport, TransOpts2, Reason)
end;
Socket ->
Socket
end,
{ok, Addr} = Transport:sockname(LSocket),
ranch_server:set_addr(Ref, Addr),
Procs = [
{{acceptor, self(), N}, {ranch_acceptor, start_link, [
LSocket, Transport, ConnsSup
]}, permanent, brutal_kill, worker, []}
|| N <- lists:seq(1, NbAcceptors)],
{ok, {{one_for_one, 10, 10}, Procs}}.
-spec listen_error(any(), module(), any(), atom()) -> no_return().
listen_error(Ref, Transport, TransOpts2, Reason) ->
error_logger:error_msg(
"Failed to start Ranch listener ~p in ~p:listen(~p) for reason ~p (~s)~n",
[Ref, Transport, TransOpts2, Reason, inet:format_error(Reason)]),
exit({listen_error, Ref, Reason}).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_app).
-behaviour(application).
-export([start/2]).
-export([stop/1]).
-export([profile_output/0]).
start(_, _) ->
_ = consider_profiling(),
ranch_sup:start_link().
stop(_) ->
ok.
-spec profile_output() -> ok.
profile_output() ->
eprof:stop_profiling(),
eprof:log("procs.profile"),
eprof:analyze(procs),
eprof:log("total.profile"),
eprof:analyze(total).
consider_profiling() ->
case application:get_env(profile) of
{ok, true} ->
{ok, _Pid} = eprof:start(),
eprof:start_profiling([self()]);
_ ->
not_profiling
end.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% Make sure to never reload this module outside a release upgrade,
%% as calling l(ranch_conns_sup) twice will kill the process and all
%% the currently open connections.
-module(ranch_conns_sup).
%% API.
-export([start_link/6]).
-export([start_protocol/2]).
-export([active_connections/1]).
%% Supervisor internals.
-export([init/7]).
-export([system_continue/3]).
-export([system_terminate/4]).
-export([system_code_change/4]).
-type conn_type() :: worker | supervisor.
-type shutdown() :: brutal_kill | timeout().
-record(state, {
parent = undefined :: pid(),
ref :: ranch:ref(),
conn_type :: conn_type(),
shutdown :: shutdown(),
transport = undefined :: module(),
protocol = undefined :: module(),
opts :: any(),
ack_timeout :: timeout(),
max_conns = undefined :: ranch:max_conns()
}).
%% API.
-spec start_link(ranch:ref(), conn_type(), shutdown(), module(),
timeout(), module()) -> {ok, pid()}.
start_link(Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol) ->
proc_lib:start_link(?MODULE, init,
[self(), Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol]).
%% We can safely assume we are on the same node as the supervisor.
%%
%% We can also safely avoid having a monitor and a timeout here
%% because only three things can happen:
%% * The supervisor died; rest_for_one strategy killed all acceptors
%% so this very calling process is going to di--
%% * There's too many connections, the supervisor will resume the
%% acceptor only when we get below the limit again.
%% * The supervisor is overloaded, there's either too many acceptors
%% or the max_connections limit is too large. It's better if we
%% don't keep accepting connections because this leaves
%% more room for the situation to be resolved.
%%
%% We do not need the reply, we only need the ok from the supervisor
%% to continue. The supervisor sends its own pid when the acceptor can
%% continue.
-spec start_protocol(pid(), inet:socket()) -> ok.
start_protocol(SupPid, Socket) ->
SupPid ! {?MODULE, start_protocol, self(), Socket},
receive SupPid -> ok end.
%% We can't make the above assumptions here. This function might be
%% called from anywhere.
-spec active_connections(pid()) -> non_neg_integer().
active_connections(SupPid) ->
Tag = erlang:monitor(process, SupPid),
catch erlang:send(SupPid, {?MODULE, active_connections, self(), Tag},
[noconnect]),
receive
{Tag, Ret} ->
erlang:demonitor(Tag, [flush]),
Ret;
{'DOWN', Tag, _, _, noconnection} ->
exit({nodedown, node(SupPid)});
{'DOWN', Tag, _, _, Reason} ->
exit(Reason)
after 5000 ->
erlang:demonitor(Tag, [flush]),
exit(timeout)
end.
%% Supervisor internals.
-spec init(pid(), ranch:ref(), conn_type(), shutdown(),
module(), timeout(), module()) -> no_return().
init(Parent, Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol) ->
process_flag(trap_exit, true),
ok = ranch_server:set_connections_sup(Ref, self()),
MaxConns = ranch_server:get_max_connections(Ref),
Opts = ranch_server:get_protocol_options(Ref),
ok = proc_lib:init_ack(Parent, {ok, self()}),
loop(#state{parent=Parent, ref=Ref, conn_type=ConnType,
shutdown=Shutdown, transport=Transport, protocol=Protocol,
opts=Opts, ack_timeout=AckTimeout, max_conns=MaxConns}, 0, 0, []).
loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
transport=Transport, protocol=Protocol, opts=Opts,
max_conns=MaxConns}, CurConns, NbChildren, Sleepers) ->
receive
{?MODULE, start_protocol, To, Socket} ->
try Protocol:start_link(Ref, Socket, Transport, Opts) of
{ok, Pid} ->
shoot(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
{ok, SupPid, ProtocolPid} when ConnType =:= supervisor ->
shoot(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
Ret ->
To ! self(),
error_logger:error_msg(
"Ranch listener ~p connection process start failure; "
"~p:start_link/4 returned: ~999999p~n",
[Ref, Protocol, Ret]),
Transport:close(Socket),
loop(State, CurConns, NbChildren, Sleepers)
catch Class:Reason ->
To ! self(),
error_logger:error_msg(
"Ranch listener ~p connection process start failure; "
"~p:start_link/4 crashed with reason: ~p:~999999p~n",
[Ref, Protocol, Class, Reason]),
loop(State, CurConns, NbChildren, Sleepers)
end;
{?MODULE, active_connections, To, Tag} ->
To ! {Tag, CurConns},
loop(State, CurConns, NbChildren, Sleepers);
%% Remove a connection from the count of connections.
{remove_connection, Ref} ->
loop(State, CurConns - 1, NbChildren, Sleepers);
%% Upgrade the max number of connections allowed concurrently.
%% We resume all sleeping acceptors if this number increases.
{set_max_conns, MaxConns2} when MaxConns2 > MaxConns ->
_ = [To ! self() || To <- Sleepers],
loop(State#state{max_conns=MaxConns2},
CurConns, NbChildren, []);
{set_max_conns, MaxConns2} ->
loop(State#state{max_conns=MaxConns2},
CurConns, NbChildren, Sleepers);
%% Upgrade the protocol options.
{set_opts, Opts2} ->
loop(State#state{opts=Opts2},
CurConns, NbChildren, Sleepers);
{'EXIT', Parent, Reason} ->
terminate(State, Reason, NbChildren);
{'EXIT', Pid, Reason} when Sleepers =:= [] ->
report_error(Ref, Protocol, Pid, Reason),
erase(Pid),
loop(State, CurConns - 1, NbChildren - 1, Sleepers);
%% Resume a sleeping acceptor if needed.
{'EXIT', Pid, Reason} ->
report_error(Ref, Protocol, Pid, Reason),
erase(Pid),
[To|Sleepers2] = Sleepers,
To ! self(),
loop(State, CurConns - 1, NbChildren - 1, Sleepers2);
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
{State, CurConns, NbChildren, Sleepers});
%% Calls from the supervisor module.
{'$gen_call', {To, Tag}, which_children} ->
Pids = get_keys(true),
Children = [{Protocol, Pid, ConnType, [Protocol]}
|| Pid <- Pids, is_pid(Pid)],
To ! {Tag, Children},
loop(State, CurConns, NbChildren, Sleepers);
{'$gen_call', {To, Tag}, count_children} ->
Counts = case ConnType of
worker -> [{supervisors, 0}, {workers, NbChildren}];
supervisor -> [{supervisors, NbChildren}, {workers, 0}]
end,
Counts2 = [{specs, 1}, {active, NbChildren}|Counts],
To ! {Tag, Counts2},
loop(State, CurConns, NbChildren, Sleepers);
{'$gen_call', {To, Tag}, _} ->
To ! {Tag, {error, ?MODULE}},
loop(State, CurConns, NbChildren, Sleepers);
Msg ->
error_logger:error_msg(
"Ranch listener ~p received unexpected message ~p~n",
[Ref, Msg])
end.
shoot(State=#state{ref=Ref, transport=Transport, ack_timeout=AckTimeout, max_conns=MaxConns},
CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
case Transport:controlling_process(Socket, ProtocolPid) of
ok ->
ProtocolPid ! {shoot, Ref, Transport, Socket, AckTimeout},
put(SupPid, true),
CurConns2 = CurConns + 1,
if CurConns2 < MaxConns ->
To ! self(),
loop(State, CurConns2, NbChildren + 1, Sleepers);
true ->
loop(State, CurConns2, NbChildren + 1, [To|Sleepers])
end;
{error, _} ->
Transport:close(Socket),
%% Only kill the supervised pid, because the connection's pid,
%% when different, is supposed to be sitting under it and linked.
exit(SupPid, kill),
loop(State, CurConns, NbChildren, Sleepers)
end.
-spec terminate(#state{}, any(), non_neg_integer()) -> no_return().
%% Kill all children and then exit. We unlink first to avoid
%% getting a message for each child getting killed.
terminate(#state{shutdown=brutal_kill}, Reason, _) ->
Pids = get_keys(true),
_ = [begin
unlink(P),
exit(P, kill)
end || P <- Pids],
exit(Reason);
%% Attempt to gracefully shutdown all children.
terminate(#state{shutdown=Shutdown}, Reason, NbChildren) ->
shutdown_children(),
_ = if
Shutdown =:= infinity ->
ok;
true ->
erlang:send_after(Shutdown, self(), kill)
end,
wait_children(NbChildren),
exit(Reason).
%% Monitor processes so we can know which ones have shutdown
%% before the timeout. Unlink so we avoid receiving an extra
%% message. Then send a shutdown exit signal.
shutdown_children() ->
Pids = get_keys(true),
_ = [begin
monitor(process, P),
unlink(P),
exit(P, shutdown)
end || P <- Pids],
ok.
wait_children(0) ->
ok;
wait_children(NbChildren) ->
receive
{'DOWN', _, process, Pid, _} ->
_ = erase(Pid),
wait_children(NbChildren - 1);
kill ->
Pids = get_keys(true),
_ = [exit(P, kill) || P <- Pids],
ok
end.
system_continue(_, _, {State, CurConns, NbChildren, Sleepers}) ->
loop(State, CurConns, NbChildren, Sleepers).
-spec system_terminate(any(), _, _, _) -> no_return().
system_terminate(Reason, _, _, {State, _, NbChildren, _}) ->
terminate(State, Reason, NbChildren).
system_code_change(Misc, _, _, _) ->
{ok, Misc}.
%% We use ~999999p here instead of ~w because the latter doesn't
%% support printable strings.
report_error(_, _, _, normal) ->
ok;
report_error(_, _, _, shutdown) ->
ok;
report_error(_, _, _, {shutdown, _}) ->
ok;
report_error(Ref, Protocol, Pid, Reason) ->
error_logger:error_msg(
"Ranch listener ~p had connection process started with "
"~p:start_link/4 at ~p exit with reason: ~999999p~n",
[Ref, Protocol, Pid, Reason]).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_listener_sup).
-behaviour(supervisor).
-export([start_link/6]).
-export([init/1]).
-spec start_link(ranch:ref(), non_neg_integer(), module(), any(), module(), any())
-> {ok, pid()}.
start_link(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts) ->
MaxConns = proplists:get_value(max_connections, TransOpts, 1024),
ranch_server:set_new_listener_opts(Ref, MaxConns, ProtoOpts),
supervisor:start_link(?MODULE, {
Ref, NbAcceptors, Transport, TransOpts, Protocol
}).
init({Ref, NbAcceptors, Transport, TransOpts, Protocol}) ->
AckTimeout = proplists:get_value(ack_timeout, TransOpts, 5000),
ConnType = proplists:get_value(connection_type, TransOpts, worker),
Shutdown = proplists:get_value(shutdown, TransOpts, 5000),
ChildSpecs = [
{ranch_conns_sup, {ranch_conns_sup, start_link,
[Ref, ConnType, Shutdown, Transport, AckTimeout, Protocol]},
permanent, infinity, supervisor, [ranch_conns_sup]},
{ranch_acceptors_sup, {ranch_acceptors_sup, start_link,
[Ref, NbAcceptors, Transport, TransOpts]},
permanent, infinity, supervisor, [ranch_acceptors_sup]}
],
{ok, {{rest_for_one, 10, 10}, ChildSpecs}}.
|
> > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
%% Copyright (c) 2012-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_protocol).
%% Start a new connection process for the given socket.
-callback start_link(
Ref::ranch:ref(),
Socket::any(),
Transport::module(),
ProtocolOptions::any())
-> {ok, ConnectionPid::pid()}
| {ok, SupPid::pid(), ConnectionPid::pid()}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
%% Copyright (c) 2012-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_server).
-behaviour(gen_server).
%% API.
-export([start_link/0]).
-export([set_new_listener_opts/3]).
-export([cleanup_listener_opts/1]).
-export([set_connections_sup/2]).
-export([get_connections_sup/1]).
-export([set_addr/2]).
-export([get_addr/1]).
-export([set_max_connections/2]).
-export([get_max_connections/1]).
-export([set_protocol_options/2]).
-export([get_protocol_options/1]).
-export([count_connections/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-define(TAB, ?MODULE).
-type monitors() :: [{{reference(), pid()}, any()}].
-record(state, {
monitors = [] :: monitors()
}).
%% API.
-spec start_link() -> {ok, pid()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec set_new_listener_opts(ranch:ref(), ranch:max_conns(), any()) -> ok.
set_new_listener_opts(Ref, MaxConns, Opts) ->
gen_server:call(?MODULE, {set_new_listener_opts, Ref, MaxConns, Opts}).
-spec cleanup_listener_opts(ranch:ref()) -> ok.
cleanup_listener_opts(Ref) ->
_ = ets:delete(?TAB, {addr, Ref}),
_ = ets:delete(?TAB, {max_conns, Ref}),
_ = ets:delete(?TAB, {opts, Ref}),
ok.
-spec set_connections_sup(ranch:ref(), pid()) -> ok.
set_connections_sup(Ref, Pid) ->
true = gen_server:call(?MODULE, {set_connections_sup, Ref, Pid}),
ok.
-spec get_connections_sup(ranch:ref()) -> pid().
get_connections_sup(Ref) ->
ets:lookup_element(?TAB, {conns_sup, Ref}, 2).
-spec set_addr(ranch:ref(), {inet:ip_address(), inet:port_number()}) -> ok.
set_addr(Ref, Addr) ->
gen_server:call(?MODULE, {set_addr, Ref, Addr}).
-spec get_addr(ranch:ref()) -> {inet:ip_address(), inet:port_number()}.
get_addr(Ref) ->
ets:lookup_element(?TAB, {addr, Ref}, 2).
-spec set_max_connections(ranch:ref(), ranch:max_conns()) -> ok.
set_max_connections(Ref, MaxConnections) ->
gen_server:call(?MODULE, {set_max_conns, Ref, MaxConnections}).
-spec get_max_connections(ranch:ref()) -> ranch:max_conns().
get_max_connections(Ref) ->
ets:lookup_element(?TAB, {max_conns, Ref}, 2).
-spec set_protocol_options(ranch:ref(), any()) -> ok.
set_protocol_options(Ref, ProtoOpts) ->
gen_server:call(?MODULE, {set_opts, Ref, ProtoOpts}).
-spec get_protocol_options(ranch:ref()) -> any().
get_protocol_options(Ref) ->
ets:lookup_element(?TAB, {opts, Ref}, 2).
-spec count_connections(ranch:ref()) -> non_neg_integer().
count_connections(Ref) ->
ranch_conns_sup:active_connections(get_connections_sup(Ref)).
%% gen_server.
init([]) ->
Monitors = [{{erlang:monitor(process, Pid), Pid}, Ref} ||
[Ref, Pid] <- ets:match(?TAB, {{conns_sup, '$1'}, '$2'})],
{ok, #state{monitors=Monitors}}.
handle_call({set_new_listener_opts, Ref, MaxConns, Opts}, _, State) ->
ets:insert(?TAB, {{max_conns, Ref}, MaxConns}),
ets:insert(?TAB, {{opts, Ref}, Opts}),
{reply, ok, State};
handle_call({set_connections_sup, Ref, Pid}, _,
State=#state{monitors=Monitors}) ->
case ets:insert_new(?TAB, {{conns_sup, Ref}, Pid}) of
true ->
MonitorRef = erlang:monitor(process, Pid),
{reply, true,
State#state{monitors=[{{MonitorRef, Pid}, Ref}|Monitors]}};
false ->
{reply, false, State}
end;
handle_call({set_addr, Ref, Addr}, _, State) ->
true = ets:insert(?TAB, {{addr, Ref}, Addr}),
{reply, ok, State};
handle_call({set_max_conns, Ref, MaxConns}, _, State) ->
ets:insert(?TAB, {{max_conns, Ref}, MaxConns}),
ConnsSup = get_connections_sup(Ref),
ConnsSup ! {set_max_conns, MaxConns},
{reply, ok, State};
handle_call({set_opts, Ref, Opts}, _, State) ->
ets:insert(?TAB, {{opts, Ref}, Opts}),
ConnsSup = get_connections_sup(Ref),
ConnsSup ! {set_opts, Opts},
{reply, ok, State};
handle_call(_Request, _From, State) ->
{reply, ignore, State}.
handle_cast(_Request, State) ->
{noreply, State}.
handle_info({'DOWN', MonitorRef, process, Pid, _},
State=#state{monitors=Monitors}) ->
{_, Ref} = lists:keyfind({MonitorRef, Pid}, 1, Monitors),
true = ets:delete(?TAB, {conns_sup, Ref}),
Monitors2 = lists:keydelete({MonitorRef, Pid}, 1, Monitors),
{noreply, State#state{monitors=Monitors2}};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_ssl).
-behaviour(ranch_transport).
-export([name/0]).
-export([secure/0]).
-export([messages/0]).
-export([listen/1]).
-export([listen_options/0]).
-export([accept/2]).
-export([accept_ack/2]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
-export([send/2]).
-export([sendfile/2]).
-export([sendfile/4]).
-export([sendfile/5]).
-export([setopts/2]).
-export([controlling_process/2]).
-export([peername/1]).
-export([sockname/1]).
-export([shutdown/2]).
-export([close/1]).
-type ssl_opt() :: {alpn_preferred_protocols, [binary()]}
| {cacertfile, string()}
| {cacerts, [public_key:der_encoded()]}
| {cert, public_key:der_encoded()}
| {certfile, string()}
| {ciphers, [ssl:erl_cipher_suite()] | string()}
| {client_renegotiation, boolean()}
| {crl_cache, {module(), {internal | any(), list()}}}
| {crl_check, boolean() | peer | best_effort}
| {depth, 0..255}
| {dh, public_key:der_encoded()}
| {dhfile, string()}
| {fail_if_no_peer_cert, boolean()}
| {hibernate_after, integer() | undefined}
| {honor_cipher_order, boolean()}
| {key, {'RSAPrivateKey' | 'DSAPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()}}
| {keyfile, string()}
| {log_alert, boolean()}
| {next_protocols_advertised, [binary()]}
| {partial_chain, fun(([public_key:der_encoded()]) -> {trusted_ca, public_key:der_encoded()} | unknown_ca)}
| {password, string()}
| {psk_identity, string()}
| {reuse_session, fun()}
| {reuse_sessions, boolean()}
| {secure_renegotiate, boolean()}
| {sni_fun, fun()}
| {sni_hosts, [{string(), ssl_opt()}]}
| {user_lookup_fun, {fun(), any()}}
| {verify, ssl:verify_type()}
| {verify_fun, {fun(), any()}}
| {versions, [atom()]}.
-export_type([ssl_opt/0]).
-type opt() :: ranch_tcp:opt() | ssl_opt().
-export_type([opt/0]).
-type opts() :: [opt()].
-export_type([opts/0]).
name() -> ssl.
-spec secure() -> boolean().
secure() ->
true.
messages() -> {ssl, ssl_closed, ssl_error}.
-spec listen(opts()) -> {ok, ssl:sslsocket()} | {error, atom()}.
listen(Opts) ->
ranch:require([crypto, asn1, public_key, ssl]),
true = lists:keymember(cert, 1, Opts)
orelse lists:keymember(certfile, 1, Opts),
Opts2 = ranch:set_option_default(Opts, backlog, 1024),
Opts3 = ranch:set_option_default(Opts2, ciphers, unbroken_cipher_suites()),
Opts4 = ranch:set_option_default(Opts3, nodelay, true),
Opts5 = ranch:set_option_default(Opts4, send_timeout, 30000),
Opts6 = ranch:set_option_default(Opts5, send_timeout_close, true),
%% We set the port to 0 because it is given in the Opts directly.
%% The port in the options takes precedence over the one in the
%% first argument.
ssl:listen(0, ranch:filter_options(Opts6, listen_options(),
[binary, {active, false}, {packet, raw},
{reuseaddr, true}, {nodelay, true}])).
listen_options() ->
[alpn_preferred_protocols, cacertfile, cacerts, cert, certfile,
ciphers, client_renegotiation, crl_cache, crl_check, depth,
dh, dhfile, fail_if_no_peer_cert, hibernate_after, honor_cipher_order,
key, keyfile, log_alert, next_protocols_advertised, partial_chain,
password, psk_identity, reuse_session, reuse_sessions, secure_renegotiate,
sni_fun, sni_hosts, user_lookup_fun, verify, verify_fun, versions
|ranch_tcp:listen_options()].
-spec accept(ssl:sslsocket(), timeout())
-> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}.
accept(LSocket, Timeout) ->
ssl:transport_accept(LSocket, Timeout).
-spec accept_ack(ssl:sslsocket(), timeout()) -> ok.
accept_ack(CSocket, Timeout) ->
case ssl:ssl_accept(CSocket, Timeout) of
ok ->
ok;
%% Garbage was most likely sent to the socket, don't error out.
{error, {tls_alert, _}} ->
ok = close(CSocket),
exit(normal);
%% Socket most likely stopped responding, don't error out.
{error, Reason} when Reason =:= timeout; Reason =:= closed ->
ok = close(CSocket),
exit(normal);
{error, Reason} ->
ok = close(CSocket),
error(Reason)
end.
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts) when is_integer(Port) ->
ssl:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}]).
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any(), timeout())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
ssl:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}],
Timeout).
-spec recv(ssl:sslsocket(), non_neg_integer(), timeout())
-> {ok, any()} | {error, closed | atom()}.
recv(Socket, Length, Timeout) ->
ssl:recv(Socket, Length, Timeout).
-spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}.
send(Socket, Packet) ->
ssl:send(Socket, Packet).
-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(),
non_neg_integer(), non_neg_integer())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, File, Offset, Bytes) ->
sendfile(Socket, File, Offset, Bytes, []).
%% Unlike with TCP, no syscall can be used here, so sending files
%% through SSL will be much slower in comparison. Note that unlike
%% file:sendfile/5 this function accepts either a file or a file name.
-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(),
non_neg_integer(), non_neg_integer(), ranch_transport:sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, File, Offset, Bytes, Opts) ->
ranch_transport:sendfile(?MODULE, Socket, File, Offset, Bytes, Opts).
%% @todo Probably filter Opts?
-spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}.
setopts(Socket, Opts) ->
ssl:setopts(Socket, Opts).
-spec controlling_process(ssl:sslsocket(), pid())
-> ok | {error, closed | not_owner | atom()}.
controlling_process(Socket, Pid) ->
ssl:controlling_process(Socket, Pid).
-spec peername(ssl:sslsocket())
-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
peername(Socket) ->
ssl:peername(Socket).
-spec sockname(ssl:sslsocket())
-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
sockname(Socket) ->
ssl:sockname(Socket).
-spec shutdown(ssl:sslsocket(), read | write | read_write)
-> ok | {error, atom()}.
shutdown(Socket, How) ->
ssl:shutdown(Socket, How).
-spec close(ssl:sslsocket()) -> ok.
close(Socket) ->
ssl:close(Socket).
%% Internal.
%% Unfortunately the implementation of elliptic-curve ciphers that has
%% been introduced in R16B01 is incomplete. Depending on the particular
%% client, this can cause the TLS handshake to break during key
%% agreement. Depending on the ssl application version, this function
%% returns a list of all cipher suites that are supported by default,
%% minus the elliptic-curve ones.
-spec unbroken_cipher_suites() -> [ssl:erl_cipher_suite()].
unbroken_cipher_suites() ->
case proplists:get_value(ssl_app, ssl:versions()) of
Version when Version =:= "5.3"; Version =:= "5.3.1" ->
lists:filter(fun(Suite) ->
string:left(atom_to_list(element(1, Suite)), 4) =/= "ecdh"
end, ssl:cipher_suites());
_ ->
ssl:cipher_suites()
end.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-spec start_link() -> {ok, pid()}.
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
ranch_server = ets:new(ranch_server, [
ordered_set, public, named_table]),
Procs = [
{ranch_server, {ranch_server, start_link, []},
permanent, 5000, worker, [ranch_server]}
],
{ok, {{one_for_one, 10, 10}, Procs}}.
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
%% Copyright (c) 2011-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_tcp).
-behaviour(ranch_transport).
-export([name/0]).
-export([secure/0]).
-export([messages/0]).
-export([listen/1]).
-export([listen_options/0]).
-export([accept/2]).
-export([accept_ack/2]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
-export([send/2]).
-export([sendfile/2]).
-export([sendfile/4]).
-export([sendfile/5]).
-export([setopts/2]).
-export([controlling_process/2]).
-export([peername/1]).
-export([sockname/1]).
-export([shutdown/2]).
-export([close/1]).
-type opt() :: {backlog, non_neg_integer()}
| {buffer, non_neg_integer()}
| {delay_send, boolean()}
| {dontroute, boolean()}
| {exit_on_close, boolean()}
| {fd, non_neg_integer()}
| {high_msgq_watermark, non_neg_integer()}
| {high_watermark, non_neg_integer()}
| inet
| inet6
| {ip, inet:ip_address()}
| {keepalive, boolean()}
| {linger, {boolean(), non_neg_integer()}}
| {low_msgq_watermark, non_neg_integer()}
| {low_watermark, non_neg_integer()}
| {nodelay, boolean()}
| {port, inet:port_number()}
| {priority, integer()}
| {raw, non_neg_integer(), non_neg_integer(), binary()}
| {recbuf, non_neg_integer()}
| {send_timeout, timeout()}
| {send_timeout_close, boolean()}
| {sndbuf, non_neg_integer()}
| {tos, integer()}.
-export_type([opt/0]).
-type opts() :: [opt()].
-export_type([opts/0]).
name() -> tcp.
-spec secure() -> boolean().
secure() ->
false.
messages() -> {tcp, tcp_closed, tcp_error}.
-spec listen(opts()) -> {ok, inet:socket()} | {error, atom()}.
listen(Opts) ->
Opts2 = ranch:set_option_default(Opts, backlog, 1024),
Opts3 = ranch:set_option_default(Opts2, nodelay, true),
Opts4 = ranch:set_option_default(Opts3, send_timeout, 30000),
Opts5 = ranch:set_option_default(Opts4, send_timeout_close, true),
%% We set the port to 0 because it is given in the Opts directly.
%% The port in the options takes precedence over the one in the
%% first argument.
gen_tcp:listen(0, ranch:filter_options(Opts5, listen_options(),
[binary, {active, false}, {packet, raw}, {reuseaddr, true}])).
%% 'inet' and 'inet6' are also allowed but they are handled
%% specifically as they do not have 2-tuple equivalents.
%%
%% The 4-tuple 'raw' option is also handled specifically.
listen_options() ->
[backlog, buffer, delay_send, dontroute, exit_on_close, fd,
high_msgq_watermark, high_watermark, ip,
keepalive, linger, low_msgq_watermark,
low_watermark, nodelay, port, priority, recbuf,
send_timeout, send_timeout_close, sndbuf, tos].
-spec accept(inet:socket(), timeout())
-> {ok, inet:socket()} | {error, closed | timeout | atom()}.
accept(LSocket, Timeout) ->
gen_tcp:accept(LSocket, Timeout).
-spec accept_ack(inet:socket(), timeout()) -> ok.
accept_ack(_, _) ->
ok.
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts) when is_integer(Port) ->
gen_tcp:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}]).
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any(), timeout())
-> {ok, inet:socket()} | {error, atom()}.
connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
gen_tcp:connect(Host, Port,
Opts ++ [binary, {active, false}, {packet, raw}],
Timeout).
-spec recv(inet:socket(), non_neg_integer(), timeout())
-> {ok, any()} | {error, closed | atom()}.
recv(Socket, Length, Timeout) ->
gen_tcp:recv(Socket, Length, Timeout).
-spec send(inet:socket(), iodata()) -> ok | {error, atom()}.
send(Socket, Packet) ->
gen_tcp:send(Socket, Packet).
-spec sendfile(inet:socket(), file:name_all() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename) ->
sendfile(Socket, Filename, 0, 0, []).
-spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, File, Offset, Bytes) ->
sendfile(Socket, File, Offset, Bytes, []).
-spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(),
non_neg_integer(), [{chunk_size, non_neg_integer()}])
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Socket, Filename, Offset, Bytes, Opts)
when is_list(Filename) orelse is_atom(Filename)
orelse is_binary(Filename) ->
case file:open(Filename, [read, raw, binary]) of
{ok, RawFile} ->
try sendfile(Socket, RawFile, Offset, Bytes, Opts) of
Result -> Result
after
ok = file:close(RawFile)
end;
{error, _} = Error ->
Error
end;
sendfile(Socket, RawFile, Offset, Bytes, Opts) ->
Opts2 = case Opts of
[] -> [{chunk_size, 16#1FFF}];
_ -> Opts
end,
try file:sendfile(RawFile, Socket, Offset, Bytes, Opts2) of
Result -> Result
catch
error:{badmatch, {error, enotconn}} ->
%% file:sendfile/5 might fail by throwing a
%% {badmatch, {error, enotconn}}. This is because its
%% implementation fails with a badmatch in
%% prim_file:sendfile/10 if the socket is not connected.
{error, closed}
end.
%% @todo Probably filter Opts?
-spec setopts(inet:socket(), list()) -> ok | {error, atom()}.
setopts(Socket, Opts) ->
inet:setopts(Socket, Opts).
-spec controlling_process(inet:socket(), pid())
-> ok | {error, closed | not_owner | atom()}.
controlling_process(Socket, Pid) ->
gen_tcp:controlling_process(Socket, Pid).
-spec peername(inet:socket())
-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
peername(Socket) ->
inet:peername(Socket).
-spec sockname(inet:socket())
-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
sockname(Socket) ->
inet:sockname(Socket).
-spec shutdown(inet:socket(), read | write | read_write)
-> ok | {error, atom()}.
shutdown(Socket, How) ->
gen_tcp:shutdown(Socket, How).
-spec close(inet:socket()) -> ok.
close(Socket) ->
gen_tcp:close(Socket).
|
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
%% Copyright (c) 2012-2015, Loïc Hoguin <essen@ninenines.eu>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(ranch_transport).
-export([sendfile/6]).
-type socket() :: any().
-type opts() :: any().
-type sendfile_opts() :: [{chunk_size, non_neg_integer()}].
-export_type([sendfile_opts/0]).
-callback name() -> atom().
-callback secure() -> boolean().
-callback messages() -> {OK::atom(), Closed::atom(), Error::atom()}.
-callback listen(opts()) -> {ok, socket()} | {error, atom()}.
-callback accept(socket(), timeout())
-> {ok, socket()} | {error, closed | timeout | atom()}.
-callback accept_ack(socket(), timeout()) -> ok.
-callback connect(string(), inet:port_number(), opts())
-> {ok, socket()} | {error, atom()}.
-callback connect(string(), inet:port_number(), opts(), timeout())
-> {ok, socket()} | {error, atom()}.
-callback recv(socket(), non_neg_integer(), timeout())
-> {ok, any()} | {error, closed | timeout | atom()}.
-callback send(socket(), iodata()) -> ok | {error, atom()}.
-callback sendfile(socket(), file:name() | file:fd())
-> {ok, non_neg_integer()} | {error, atom()}.
-callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(),
non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}.
-callback sendfile(socket(), file:name() | file:fd(), non_neg_integer(),
non_neg_integer(), sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
-callback setopts(socket(), opts()) -> ok | {error, atom()}.
-callback controlling_process(socket(), pid())
-> ok | {error, closed | not_owner | atom()}.
-callback peername(socket())
-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
-callback sockname(socket())
-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
-callback shutdown(socket(), read | write | read_write)
-> ok | {error, atom()}.
-callback close(socket()) -> ok.
%% A fallback for transports that don't have a native sendfile implementation.
%% Note that the ordering of arguments is different from file:sendfile/5 and
%% that this function accepts either a raw file or a file name.
-spec sendfile(module(), socket(), file:filename_all() | file:fd(),
non_neg_integer(), non_neg_integer(), sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(Transport, Socket, Filename, Offset, Bytes, Opts)
when is_list(Filename) orelse is_atom(Filename)
orelse is_binary(Filename) ->
ChunkSize = chunk_size(Opts),
case file:open(Filename, [read, raw, binary]) of
{ok, RawFile} ->
_ = case Offset of
0 ->
ok;
_ ->
{ok, _} = file:position(RawFile, {bof, Offset})
end,
try
sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize)
after
ok = file:close(RawFile)
end;
{error, _Reason} = Error ->
Error
end;
sendfile(Transport, Socket, RawFile, Offset, Bytes, Opts) ->
ChunkSize = chunk_size(Opts),
Initial2 = case file:position(RawFile, {cur, 0}) of
{ok, Offset} ->
Offset;
{ok, Initial} ->
{ok, _} = file:position(RawFile, {bof, Offset}),
Initial
end,
case sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) of
{ok, _Sent} = Result ->
{ok, _} = file:position(RawFile, {bof, Initial2}),
Result;
{error, _Reason} = Error ->
Error
end.
-spec chunk_size(sendfile_opts()) -> pos_integer().
chunk_size(Opts) ->
case lists:keyfind(chunk_size, 1, Opts) of
{chunk_size, ChunkSize}
when is_integer(ChunkSize) andalso ChunkSize > 0 ->
ChunkSize;
{chunk_size, 0} ->
16#1FFF;
false ->
16#1FFF
end.
-spec sendfile_loop(module(), socket(), file:fd(), non_neg_integer(),
non_neg_integer(), pos_integer())
-> {ok, non_neg_integer()} | {error, term()}.
sendfile_loop(_Transport, _Socket, _RawFile, Sent, Sent, _ChunkSize)
when Sent =/= 0 ->
%% All requested data has been read and sent, return number of bytes sent.
{ok, Sent};
sendfile_loop(Transport, Socket, RawFile, Bytes, Sent, ChunkSize) ->
ReadSize = read_size(Bytes, Sent, ChunkSize),
case file:read(RawFile, ReadSize) of
{ok, IoData} ->
case Transport:send(Socket, IoData) of
ok ->
Sent2 = iolist_size(IoData) + Sent,
sendfile_loop(Transport, Socket, RawFile, Bytes, Sent2,
ChunkSize);
{error, _Reason} = Error ->
Error
end;
eof ->
{ok, Sent};
{error, _Reason} = Error ->
Error
end.
-spec read_size(non_neg_integer(), non_neg_integer(), non_neg_integer()) ->
non_neg_integer().
read_size(0, _Sent, ChunkSize) ->
ChunkSize;
read_size(Bytes, Sent, ChunkSize) ->
min(Bytes - Sent, ChunkSize).
|
> > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
defmodule Antigone do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [
worker(__MODULE__, [], function: :serve),
]
opts = [strategy: :one_for_one, name: Antigone.Supervisor]
Supervisor.start_link(children, opts)
end
def serve do
{:ok,_} = Plug.Adapters.Cowboy.http Router, []
end
end
|
> > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
defmodule Router do
use Plug.Router
if Mix.env == :dev do
use Plug.Debugger
end
plug :match
plug :dispatch
get "/" do
send_file(conn, 200, "www/index.html")
end
forward "/", to: Static
end
|
> > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 |
defmodule Static do
use Plug.Builder
plug Plug.Static, at: "/", from: "www"
plug :not_found
def not_found(conn, _) do
send_file(conn, 404, "www/404.html")
end
end
|
> > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
defmodule Antigone.Mixfile do
use Mix.Project
def project do
[app: :antigone,
version: "0.0.1",
elixir: "~> 1.1",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
deps: deps]
end
def application do
[applications: [:logger, :cowboy, :plug],
mod: {Antigone, []}]
end
defp deps do
[
{:cowboy, "~> 1.0.4"},
{:plug, "~> 1.0.3"}
]
end
end
|
> > > > | 1 2 3 4 |
%{"cowboy": {:hex, :cowboy, "1.0.4"},
"cowlib": {:hex, :cowlib, "1.0.2"},
"plug": {:hex, :plug, "1.0.3"},
"ranch": {:hex, :ranch, "1.2.0"}}
|
> > > > > > > > | 1 2 3 4 5 6 7 8 |
defmodule AntigoneTest do
use ExUnit.Case
doctest Antigone
test "the truth" do
assert 1 + 1 == 2
end
end
|
> | 1 | ExUnit.start() |
> > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!DOCTYPE html>
<html>
<head>
<title>Error: 404</title>
</head>
<body>
<header>
<h1>Error</h1>
</header>
<main>
<h2>404: File Not Found</h2>
<p>We could not find the content you requested.</p>
<p>Please check your URL and try again.</p>
</main>
<footer>
<p>This is an example file. Please change to suit your need</p>
</footer>
</body>
</html>
|
> > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | Copyright (c) 2015, Nathaniel Alcock Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<title>yellowfoxco</title>
<link rel='stylesheet' type='text/css' href='style.css' />
</head>
<body>
<header>
<h1>YELLOWFOXCO</h1>
<img src='logo.jpg' alt='Logo' />
<nav>
<a href='https://etsy.com/shop/YELLOWFOXCO'>Shop</a>
<a href='#connect'>Connect</a>
<a href='https://yellowfoxco.tumblr.com'>Blog</a>
</nav>
</header>
<main>
<h2 id='connect'>PASSION • MEETS • DESIGN</h2>
<p>
YELLOWFOXCO was created for the creative. Turning home decor
into a passion and letting inspiration unfold. Working hard &
getting our hands dirty is what we do, design is what we love.
We are the wild creative type, and so are our customers. We
design for you in mind, making your dream home come true.
</p>
<img src='shelf.jpg' alt='Shelf' />
<p>
You can view our work on our
<a href='https://etsy.com/shop/YELLOWFOXCO'>Etsy</a> shop. We
write on
<a href='https://yellowfoxco.tumblr.com'>Tumblr</a> and post
photos to <a href='https://instagram.com/yellowfoxco/'>
Instagram</a> You can also find us on
<a href='https://facebook.com/YellowFoxCo'>Facebook</a>.
</p>
<p>
We often make custom orders. If you've got an idea,
send us an <a href='mailto:emily@yellowfoxco.com'>email</a>
to get in touch. You can also use the following form:
</p>
<form action='http://formspree.io/emily@yellowfoxco.com' method='POST'>
<input type='text' name='name' placeholder='Your Name' />
<input type='email' name='_replyto' placeholder='Email Address' />
<textarea placeholder='Tell us about your project!' rows='5'>
</textarea>
<input id='submit' type='submit' value='Send' />
</form>
</main>
<footer>
©
2016
YELLOWFOXCO
</footer>
</body>
</html>
|
> > | 1 2 |
// Generated by CoffeeScript 1.9.3
(function(){var e;e=function(){function e(e,t){var n,r;this.options={target:"instafeed",get:"popular",resolution:"thumbnail",sortBy:"none",links:!0,mock:!1,useHttp:!1};if(typeof e=="object")for(n in e)r=e[n],this.options[n]=r;this.context=t!=null?t:this,this.unique=this._genKey()}return e.prototype.hasNext=function(){return typeof this.context.nextUrl=="string"&&this.context.nextUrl.length>0},e.prototype.next=function(){return this.hasNext()?this.run(this.context.nextUrl):!1},e.prototype.run=function(t){var n,r,i;if(typeof this.options.clientId!="string"&&typeof this.options.accessToken!="string")throw new Error("Missing clientId or accessToken.");if(typeof this.options.accessToken!="string"&&typeof this.options.clientId!="string")throw new Error("Missing clientId or accessToken.");return this.options.before!=null&&typeof this.options.before=="function"&&this.options.before.call(this),typeof document!="undefined"&&document!==null&&(i=document.createElement("script"),i.id="instafeed-fetcher",i.src=t||this._buildUrl(),n=document.getElementsByTagName("head"),n[0].appendChild(i),r="instafeedCache"+this.unique,window[r]=new e(this.options,this),window[r].unique=this.unique),!0},e.prototype.parse=function(e){var t,n,r,i,s,o,u,a,f,l,c,h,p,d,v,m,g,y,b,w,E,S,x,T,N,C,k,L,A,O,M,_,D;if(typeof e!="object"){if(this.options.error!=null&&typeof this.options.error=="function")return this.options.error.call(this,"Invalid JSON data"),!1;throw new Error("Invalid JSON response")}if(e.meta.code!==200){if(this.options.error!=null&&typeof this.options.error=="function")return this.options.error.call(this,e.meta.error_message),!1;throw new Error("Error from Instagram: "+e.meta.error_message)}if(e.data.length===0){if(this.options.error!=null&&typeof this.options.error=="function")return this.options.error.call(this,"No images were returned from Instagram"),!1;throw new Error("No images were returned from Instagram")}this.options.success!=null&&typeof this.options.success=="function"&&this.options.success.call(this,e),this.context.nextUrl="",e.pagination!=null&&(this.context.nextUrl=e.pagination.next_url);if(this.options.sortBy!=="none"){this.options.sortBy==="random"?M=["","random"]:M=this.options.sortBy.split("-"),O=M[0]==="least"?!0:!1;switch(M[1]){case"random":e.data.sort(function(){return.5-Math.random()});break;case"recent":e.data=this._sortBy(e.data,"created_time",O);break;case"liked":e.data=this._sortBy(e.data,"likes.count",O);break;case"commented":e.data=this._sortBy(e.data,"comments.count",O);break;default:throw new Error("Invalid option for sortBy: '"+this.options.sortBy+"'.")}}if(typeof document!="undefined"&&document!==null&&this.options.mock===!1){m=e.data,A=parseInt(this.options.limit,10),this.options.limit!=null&&m.length>A&&(m=m.slice(0,A)),u=document.createDocumentFragment(),this.options.filter!=null&&typeof this.options.filter=="function"&&(m=this._filter(m,this.options.filter));if(this.options.template!=null&&typeof this.options.template=="string"){f="",d="",w="",D=document.createElement("div");for(c=0,N=m.length;c<N;c++){h=m[c],p=h.images[this.options.resolution];if(typeof p!="object")throw o="No image found for resolution: "+this.options.resolution+".",new Error(o);E=p.width,y=p.height,b="square",E>y&&(b="landscape"),E<y&&(b="portrait"),v=p.url,l=window.location.protocol.indexOf("http")>=0,l&&!this.options.useHttp&&(v=v.replace(/https?:\/\//,"//")),d=this._makeTemplate(this.options.template,{model:h,id:h.id,link:h.link,type:h.type,image:v,width:E,height:y,orientation:b,caption:this._getObjectProperty(h,"caption.text"),likes:h.likes.count,comments:h.comments.count,location:this._getObjectProperty(h,"location.name")}),f+=d}D.innerHTML=f,i=[],r=0,n=D.childNodes.length;while(r<n)i.push(D.childNodes[r]),r+=1;for(x=0,C=i.length;x<C;x++)L=i[x],u.appendChild(L)}else for(T=0,k=m.length;T<k;T++){h=m[T],g=document.createElement("img"),p=h.images[this.options.resolution];if(typeof p!="object")throw o="No image found for resolution: "+this.options.resolution+".",new Error(o);v=p.url,l=window.location.protocol.indexOf("http")>=0,l&&!this.options.useHttp&&(v=v.replace(/https?:\/\//,"//")),g.src=v,this.options.links===!0?(t=document.createElement("a"),t.href=h.link,t.appendChild(g),u.appendChild(t)):u.appendChild(g)}_=this.options.target,typeof _=="string"&&(_=document.getElementById(_));if(_==null)throw o='No element with id="'+this.options.target+'" on page.',new Error(o);_.appendChild(u),a=document.getElementsByTagName("head")[0],a.removeChild(document.getElementById("instafeed-fetcher")),S="instafeedCache"+this.unique,window[S]=void 0;try{delete window[S]}catch(P){s=P}}return this.options.after!=null&&typeof this.options.after=="function"&&this.options.after.call(this),!0},e.prototype._buildUrl=function(){var e,t,n;e="https://api.instagram.com/v1";switch(this.options.get){case"popular":t="media/popular";break;case"tagged":if(!this.options.tagName)throw new Error("No tag name specified. Use the 'tagName' option.");t="tags/"+this.options.tagName+"/media/recent";break;case"location":if(!this.options.locationId)throw new Error("No location specified. Use the 'locationId' option.");t="locations/"+this.options.locationId+"/media/recent";break;case"user":if(!this.options.userId)throw new Error("No user specified. Use the 'userId' option.");t="users/"+this.options.userId+"/media/recent";break;default:throw new Error("Invalid option for get: '"+this.options.get+"'.")}return n=e+"/"+t,this.options.accessToken!=null?n+="?access_token="+this.options.accessToken:n+="?client_id="+this.options.clientId,this.options.limit!=null&&(n+="&count="+this.options.limit),n+="&callback=instafeedCache"+this.unique+".parse",n},e.prototype._genKey=function(){var e;return e=function(){return((1+Math.random())*65536|0).toString(16).substring(1)},""+e()+e()+e()+e()},e.prototype._makeTemplate=function(e,t){var n,r,i,s,o;r=/(?:\{{2})([\w\[\]\.]+)(?:\}{2})/,n=e;while(r.test(n))s=n.match(r)[1],o=(i=this._getObjectProperty(t,s))!=null?i:"",n=n.replace(r,function(){return""+o});return n},e.prototype._getObjectProperty=function(e,t){var n,r;t=t.replace(/\[(\w+)\]/g,".$1"),r=t.split(".");while(r.length){n=r.shift();if(!(e!=null&&n in e))return null;e=e[n]}return e},e.prototype._sortBy=function(e,t,n){var r;return r=function(e,r){var i,s;return i=this._getObjectProperty(e,t),s=this._getObjectProperty(r,t),n?i>s?1:-1:i<s?1:-1},e.sort(r.bind(this)),e},e.prototype._filter=function(e,t){var n,r,i,s,o;n=[],r=function(e){if(t(e))return n.push(e)};for(i=0,o=e.length;i<o;i++)s=e[i],r(s);return n},e}(),function(e,t){return typeof define=="function"&&define.amd?define([],t):typeof module=="object"&&module.exports?module.exports=t():e.Instafeed=t()}(this,function(){return e})}).call(this);
|
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
:root {
--dark-white: #bdc3c7;
--light-white: #ecf0f1;
--dark-grey: #7f8c8d;
--light-grey: #95a5a6;
--dark-black: #212121;
--light-black: #424242;
--dark-red: #c0392b;
--light-red: #e74c3c;
--dark-orange: #d35400;
--light-orange: #e67e22;
--dark-yellow: #f39c12;
--light-yellow: #f1c40f;
--dark-green: #27ae60;
--light-green: #2ecc71;
--dark-blue: #2980b9;
--light-blue: #3498db;
--dark-indigo: #2c3e50;
--light-indigo: #34495e;
--dark-violet: #8e44ad;
--light-violet: #9b59b6;
}
body {
color: var(--dark-black);
font-family: helvetica, arial, verdana, sans-serif;
font-size: 1.5em;
line-height: 1.5;
margin: auto;
width: 95%;
}
header {
display: block;
height: 100%;
}
h1 {
border-top: 0.2ch solid;
border-bottom: 0.2ch solid;
display: block;
margin: 2ch auto;
padding: 0.5ch;
text-align: center;
width: 14ch;
}
nav {
margin: 5% auto;
text-align: center;
}
nav a {
color: var(--dark-black);
display: inline-block;
font-size: 1.5em;
margin: 2ch;
max-width: 7ch;
}
header img {
border-radius: 300px;
display: block;
margin: 5% auto;
width: 300px;
}
main {
margin: 10% auto;
width: 95%;
}
h2 {
color: var(--dark-white);
font-size: 1em;
font-weight: normal;
letter-spacing: 0.4ch;
margin: 2ch auto 5ch auto;
text-align: center;
}
p {
margin: 3ch auto;
max-width: 41ch;
}
main img {
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
display: block;
margin: 5ch auto;
width: 320px;
}
a {
border-bottom: 0.2ch solid transparent;
color: var(--dark-blue);
text-decoration: none;
}
a:hover {
border-bottom: 0.2ch solid;
}
form {
margin: 5ch auto;
}
input, textarea {
font-size: 1em;
font-family: helvetica, arial, verdana, sans-serif;
display: block;
margin: 2ch auto;
padding: 1ch;
width: 30ch;
}
#submit {
width: 20ch;
}
footer {
color: var(--dark-grey);
margin: 5ch auto 2ch auto;
text-align: center;
}
@media (max-width: 650px) {
nav {
margin: 10% auto;
}
nav a {
display: block;
margin: 1ch auto;
}
h2 {
font-size: 0.9em;
}
}
|
cannot compute difference between binary files
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
body {
color: #212121;
font-family: helvetica, arial, verdana, sans-serif;
font-size: 1.5em;
line-height: 1.5;
margin: auto;
width: 95%;
}
header {
display: block;
height: 100%;
}
h1 {
border-top: 0.2ch solid;
border-bottom: 0.2ch solid;
display: block;
margin: 2ch auto;
padding: 0.5ch;
text-align: center;
width: 14ch;
}
nav {
margin: 5% auto;
text-align: center;
}
nav a {
color: #212121;
display: inline-block;
font-size: 1.5em;
margin: 2ch;
max-width: 7ch;
}
header img {
border-radius: 300px;
display: block;
margin: 5% auto;
width: 300px;
}
main {
margin: 10% auto;
width: 95%;
}
h2 {
color: #bdc3c7;
font-size: 1em;
font-weight: normal;
letter-spacing: 0.4ch;
margin: 2ch auto 5ch auto;
text-align: center;
}
p {
margin: 3ch auto;
max-width: 41ch;
}
main img {
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
display: block;
margin: 5ch auto;
width: 320px;
}
a {
border-bottom: 0.2ch solid transparent;
color: #2980b9;
text-decoration: none;
}
a:hover {
border-bottom: 0.2ch solid;
}
form {
margin: 5ch auto;
}
input,
textarea {
font-size: 1em;
font-family: helvetica, arial, verdana, sans-serif;
display: block;
margin: 2ch auto;
padding: 1ch;
width: 30ch;
}
#submit {
width: 20ch;
}
footer {
color: #7f8c8d;
margin: 5ch auto 2ch auto;
text-align: center;
}
@media (max-width: 650px) {
nav {
margin: 10% auto;
}
nav a {
display: block;
margin: 1ch auto;
}
h2 {
font-size: 0.9em;
}
}
|