rack-gem-assets 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Simon Menke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,48 @@
1
+ = Rack GemAssets
2
+
3
+ This is a Rack Middleware which will find and send static files provided by loaded gems.
4
+
5
+ === Asset search rules
6
+
7
+ 1. if the path starts with <tt>/vendor/:name</tt> (eg: <tt>/vendor/my-assets-gem/image.jpg</tt>)
8
+ we will look for a loaded gem called <tt>:name</tt> and find a file in there first.
9
+ Otherwise we search each loaded gems.
10
+ 2. For each gem in our search list we will look in the <tt>:assets_dir</tt> directory under the <tt>full_gem_path</tt> (next to the lib dir).
11
+ if this directory contains a file with the specified path we send it.
12
+ 3. if the path ends with a slash we remove it. then we append <tt>.html</tt>
13
+ (eg: <tt>/pages/about/ => /pages/about.html</tt>, <tt>/pages/about => /pages/about.html</tt>)
14
+ 4. If we still dont have a match we append <tt>/index.html</tt>
15
+ (eg: <tt>/pages/about/ => /pages/about/index.html</tt>, <tt>/pages/about => /pages/about/index.html</tt>)
16
+ 5. If we still don't have a match we pass the request to the sub app.
17
+
18
+ === Configuration
19
+
20
+ * <tt>:assets_dir</tt>: the sub directory where we will look for assets. (default: <tt>public</tt>)
21
+ * <tt>:xsendfile</tt>: use X-Sendfile to send files.(default: <tt>true</tt>)
22
+
23
+ === Rails Configuration
24
+
25
+ config.gem "rack-gem-assets", :lib => "rack/gem_assets", :source => "http://gemcutter.org"
26
+ config.middleware.use "Rack::GemAssets", :assets_dir => 'assets', :xsendfile => false
27
+
28
+ === X-Sendfile Configuration
29
+
30
+ Your apache config needs to include these configuration directives:
31
+
32
+ XSendFile On # enable X-Sendfile
33
+ XSendFileAllowAbove On # allow X-Sendfile to access files outside of the Directory
34
+
35
+ == Note on Patches/Pull Requests
36
+
37
+ * Fork the project.
38
+ * Make your feature addition or bug fix.
39
+ * Add tests for it. This is important so I don't break it in a
40
+ future version unintentionally.
41
+ * Commit, do not mess with rakefile, version, or history.
42
+ (if you want to have your own version, that is fine but
43
+ bump version in a commit by itself I can ignore when I pull)
44
+ * Send me a pull request. Bonus points for topic branches.
45
+
46
+ == Copyright
47
+
48
+ Copyright (c) 2009 Simon Menke. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rack-gem-assets"
8
+ gem.summary = %Q{See description}
9
+ gem.description = %Q{A Middleware for sending assets packaged in loaded gems}
10
+ gem.email = "simon@mrhenry.be"
11
+ gem.homepage = "http://github.com/simonmenke/rack-gem-assets"
12
+ gem.authors = ["Simon Menke"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "rack-gem-assets #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,575 @@
1
+ /* Copyright 2006 by Nils Maier
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ /*
17
+ * mod_xsendfile.c: Process X-SENDFILE header cgi/scripts may set
18
+ * Written by Nils Maier, MaierMan@web.de, March 2006
19
+ *
20
+ * Whenever an X-SENDFILE header occures in the response headers drop
21
+ * the body and send the replacement file idenfitied by this header instead.
22
+ *
23
+ * Method inspired by lighttpd <http://lighttpd.net/>
24
+ * Code inspired by mod_headers, mod_rewrite and such
25
+ *
26
+ * Configuration:
27
+ * You may turn on processing in any context, where perdir config overrides server config:
28
+ * XSendfile On|Off - Enable/disable(default) processing
29
+ *
30
+ * Installation:
31
+ * apxs2 -cia mod_xsendfile.c
32
+ */
33
+
34
+ /*
35
+ v0.9
36
+ (peer-review still required)
37
+ */
38
+
39
+ #include "apr.h"
40
+ #include "apr_lib.h"
41
+ #include "apr_strings.h"
42
+ #include "apr_buckets.h"
43
+ #include "apr_file_io.h"
44
+
45
+ #include "apr_hash.h"
46
+ #define APR_WANT_IOVEC
47
+ #define APR_WANT_STRFUNC
48
+ #include "apr_want.h"
49
+
50
+ #include "httpd.h"
51
+ #include "http_log.h"
52
+ #include "http_config.h"
53
+ #include "http_log.h"
54
+ #define CORE_PRIVATE
55
+ #include "http_request.h"
56
+ #include "http_core.h" /* needed for per-directory core-config */
57
+ #include "util_filter.h"
58
+ #include "http_protocol.h" /* ap_hook_insert_error_filter */
59
+
60
+ #define AP_XSENDFILE_HEADER "X-SENDFILE"
61
+
62
+ #if defined(__GNUC__) && (__GNUC__ > 2)
63
+ # define AP_XSENDFILE_EXPECT_TRUE(x) __builtin_expect((x), 1);
64
+ # define AP_XSENDFILE_EXPECT_FALSE(x) __builtin_expect((x), 0);
65
+ #else
66
+ # define AP_XSENDFILE_EXPECT_TRUE(x) (x)
67
+ # define AP_XSENDFILE_EXPECT_FALSE(x) (x)
68
+ #endif
69
+
70
+ module AP_MODULE_DECLARE_DATA xsendfile_module;
71
+
72
+ typedef enum {
73
+ XSENDFILE_UNSET, XSENDFILE_ENABLED, XSENDFILE_DISABLED
74
+ } xsendfile_conf_active_t;
75
+
76
+ typedef struct xsendfile_conf_t
77
+ {
78
+ xsendfile_conf_active_t enabled;
79
+ xsendfile_conf_active_t allowAbove;
80
+ } xsendfile_conf_t;
81
+
82
+ static void *xsendfile_config_server_create(apr_pool_t *p, server_rec *s)
83
+ {
84
+ xsendfile_conf_t *conf;
85
+
86
+ conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
87
+ conf->allowAbove = conf->enabled = XSENDFILE_UNSET;
88
+
89
+ return (void*)conf;
90
+ }
91
+
92
+ #define XSENDFILE_CFLAG(x) conf->x = overrides->x != XSENDFILE_UNSET ? overrides->x : base->x
93
+
94
+ static void *xsendfile_config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
95
+ {
96
+ xsendfile_conf_t *base = (xsendfile_conf_t *) basev;
97
+ xsendfile_conf_t *overrides = (xsendfile_conf_t *) overridesv;
98
+ xsendfile_conf_t *conf;
99
+
100
+ conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
101
+
102
+ XSENDFILE_CFLAG(enabled);
103
+ XSENDFILE_CFLAG(allowAbove);
104
+
105
+ return (void*)conf;
106
+ }
107
+
108
+ static void *xsendfile_config_perdir_create(apr_pool_t *p, char *path)
109
+ {
110
+ xsendfile_conf_t *conf;
111
+
112
+ conf = (xsendfile_conf_t *)apr_pcalloc(p, sizeof(xsendfile_conf_t));
113
+ conf->allowAbove = conf->enabled = XSENDFILE_UNSET;
114
+
115
+ return (void*)conf;
116
+ }
117
+
118
+ static void *xsendfile_config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
119
+ {
120
+ xsendfile_conf_t *base = (xsendfile_conf_t *) basev;
121
+ xsendfile_conf_t *overrides = (xsendfile_conf_t*)overridesv;
122
+ xsendfile_conf_t *conf;
123
+
124
+ conf = (xsendfile_conf_t*)apr_pcalloc(p, sizeof(xsendfile_conf_t));
125
+
126
+
127
+ XSENDFILE_CFLAG(enabled);
128
+ XSENDFILE_CFLAG(allowAbove);
129
+
130
+ return (void*)conf;
131
+ }
132
+ #undef XSENDFILE_CFLAG
133
+
134
+ static const char *xsendfile_cmd_flag(cmd_parms *cmd, void *perdir_confv, int flag)
135
+ {
136
+ xsendfile_conf_t *conf = (xsendfile_conf_t *)perdir_confv;
137
+ if (cmd->path == NULL)
138
+ {
139
+ conf = (xsendfile_conf_t*)ap_get_module_config(
140
+ cmd->server->module_config,
141
+ &xsendfile_module
142
+ );
143
+ }
144
+ if (conf)
145
+ {
146
+ if (strcasecmp(cmd->cmd->name, "xsendfile") == 0)
147
+ {
148
+ conf->enabled = flag ? XSENDFILE_ENABLED : XSENDFILE_DISABLED;
149
+ }
150
+ else
151
+ {
152
+ conf->allowAbove = flag ? XSENDFILE_ENABLED : XSENDFILE_DISABLED;
153
+ }
154
+ }
155
+ return NULL;
156
+ }
157
+
158
+ /*
159
+ little helper function to get the original request path
160
+
161
+ code borrowed from request.c and util_script.c
162
+ */
163
+ static const char *ap_xsendfile_get_orginal_path(request_rec *rec)
164
+ {
165
+ const char
166
+ *rv = rec->the_request,
167
+ *last;
168
+
169
+ int dir = 0;
170
+ size_t uri_len;
171
+
172
+ /* skip method && spaces */
173
+ while (*rv && !apr_isspace(*rv))
174
+ {
175
+ ++rv;
176
+ }
177
+ while (apr_isspace(*rv))
178
+ {
179
+ ++rv;
180
+ }
181
+ /* first space is the request end */
182
+ last = rv;
183
+ while (*last && !apr_isspace(*last))
184
+ {
185
+ ++last;
186
+ }
187
+ uri_len = last - rv;
188
+ if (!uri_len)
189
+ {
190
+ return NULL;
191
+ }
192
+
193
+ /* alright, lets see if the request_uri changed! */
194
+ if (strncmp(rv, rec->uri, uri_len) == 0)
195
+ {
196
+ rv = apr_pstrdup(rec->pool, rec->filename);
197
+ dir = rec->finfo.filetype == APR_DIR;
198
+ }
199
+ else
200
+ {
201
+ /* need to lookup the url again as it changed */
202
+ request_rec *sr = ap_sub_req_lookup_uri(
203
+ apr_pstrmemdup(rec->pool, rv, uri_len),
204
+ rec,
205
+ NULL
206
+ );
207
+ if (!sr)
208
+ {
209
+ return NULL;
210
+ }
211
+ rv = apr_pstrdup(rec->pool, sr->filename);
212
+ dir = rec->finfo.filetype == APR_DIR;
213
+ ap_destroy_sub_req(sr);
214
+ }
215
+
216
+ /* now we need to truncate so we only have the directory */
217
+ if (!dir && (last = ap_strrchr(rv, '/')) != NULL)
218
+ {
219
+ *((char*)last + 1) = '\0';
220
+ }
221
+
222
+ return rv;
223
+ }
224
+
225
+ static apr_status_t ap_xsendfile_output_filter(
226
+ ap_filter_t *f,
227
+ apr_bucket_brigade *in
228
+ )
229
+ {
230
+ request_rec *r = f->r, *sr = NULL;
231
+
232
+ xsendfile_conf_active_t allowAbove = ((xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module))->allowAbove;
233
+ core_dir_config *coreconf = (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module);
234
+
235
+ apr_status_t rv;
236
+ apr_bucket *e;
237
+
238
+ apr_file_t *fd = NULL;
239
+ apr_finfo_t finfo;
240
+
241
+ const char *file = NULL, *root = NULL;
242
+ char *newpath = NULL;
243
+
244
+ int errcode;
245
+
246
+ if (XSENDFILE_UNSET == allowAbove)
247
+ {
248
+ allowAbove = ((xsendfile_conf_t*)ap_get_module_config(r->server->module_config, &xsendfile_module))->allowAbove;
249
+ }
250
+
251
+ #ifdef _DEBUG
252
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: output_filter for %s", r->the_request);
253
+ #endif
254
+
255
+ /*
256
+ should we proceed with this request?
257
+
258
+ * sub-requests suck
259
+ * furthermore default-handled requests suck, as they actually shouldn't be able to set headers
260
+ */
261
+ if (
262
+ r->status != HTTP_OK
263
+ || r->main
264
+ || (r->handler && strcmp(r->handler, "default-handler") == 0) /* those table-keys are lower-case, right? */
265
+ )
266
+ {
267
+ #ifdef _DEBUG
268
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: not met [%d]", r->status);
269
+ #endif
270
+ ap_remove_output_filter(f);
271
+ return ap_pass_brigade(f->next, in);
272
+ }
273
+
274
+ /*
275
+ alright, look for x-sendfile
276
+ */
277
+ file = apr_table_get(r->headers_out, AP_XSENDFILE_HEADER);
278
+ apr_table_unset(r->headers_out, AP_XSENDFILE_HEADER);
279
+
280
+ /* cgi/fastcgi will put the stuff into err_headers_out */
281
+ if (!file || !*file)
282
+ {
283
+ file = apr_table_get(r->err_headers_out, AP_XSENDFILE_HEADER);
284
+ apr_table_unset(r->err_headers_out, AP_XSENDFILE_HEADER);
285
+ }
286
+
287
+ /* nothing there :p */
288
+ if (!file || !*file)
289
+ {
290
+ #ifdef _DEBUG
291
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: nothing found");
292
+ #endif
293
+ ap_remove_output_filter(f);
294
+ return ap_pass_brigade(f->next, in);
295
+ }
296
+
297
+ /*
298
+ drop *everything*
299
+ might be pretty expensive to generate content first that goes straight to the bitbucket,
300
+ but actually the scripts that might set this flag won't output too much anyway
301
+ */
302
+ while (!APR_BRIGADE_EMPTY(in))
303
+ {
304
+ e = APR_BRIGADE_FIRST(in);
305
+ apr_bucket_delete(e);
306
+ }
307
+ r->eos_sent = 0;
308
+
309
+ /*
310
+ grab the current path
311
+ somewhat nasty...
312
+ any better ways?
313
+
314
+ freaking fix for cgi-alike handlers that overwrite our precious values :p
315
+ */
316
+ root = ap_xsendfile_get_orginal_path(r);
317
+
318
+ #ifdef _DEBUG
319
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: path is %s", root);
320
+ #endif
321
+
322
+ /*
323
+ alright, we now build our new path
324
+ */
325
+ if ((rv = apr_filepath_merge(
326
+ &newpath,
327
+ root,
328
+ file,
329
+ APR_FILEPATH_TRUENAME | (allowAbove != XSENDFILE_ENABLED ? APR_FILEPATH_SECUREROOT : 0),
330
+ r->pool
331
+ )) != OK)
332
+ {
333
+ ap_log_rerror(
334
+ APLOG_MARK,
335
+ APLOG_ERR,
336
+ rv,
337
+ r,
338
+ "xsendfile: unable to find file: %s, aa=%d",
339
+ file, allowAbove
340
+ );
341
+ ap_remove_output_filter(f);
342
+ ap_die(HTTP_NOT_FOUND, r);
343
+ return HTTP_NOT_FOUND;
344
+ }
345
+
346
+ #ifdef _DEBUG
347
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: found %s", newpath);
348
+ #endif
349
+
350
+ /*
351
+ try open the file
352
+ */
353
+ if ((rv = apr_file_open(
354
+ &fd,
355
+ newpath,
356
+ APR_READ | APR_BINARY
357
+ #if APR_HAS_SENDFILE
358
+ | (coreconf->enable_sendfile == ENABLE_SENDFILE_ON ? APR_SENDFILE_ENABLED : 0)
359
+ #endif
360
+ ,
361
+ 0,
362
+ r->pool
363
+ )) != APR_SUCCESS)
364
+ {
365
+ ap_log_rerror(
366
+ APLOG_MARK,
367
+ APLOG_ERR,
368
+ rv,
369
+ r,
370
+ "xsendfile: cannot open file: %s",
371
+ newpath
372
+ );
373
+ ap_remove_output_filter(f);
374
+ ap_die(HTTP_NOT_FOUND, r);
375
+ return HTTP_NOT_FOUND;
376
+ }
377
+ #if APR_HAS_SENDFILE && defined(_DEBUG)
378
+ if (coreconf->enable_sendfile != ENABLE_SENDFILE_ON)
379
+ {
380
+
381
+ ap_log_error(
382
+ APLOG_MARK,
383
+ APLOG_WARNING,
384
+ 0,
385
+ r->server,
386
+ "xsendfile: sendfile configured, but not active %d",
387
+ coreconf->enable_sendfile
388
+ );
389
+ }
390
+ #endif
391
+ /*
392
+ stat (for etag/cache/content-length stuff)
393
+ */
394
+ if ((rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd)) != APR_SUCCESS)
395
+ {
396
+ ap_log_rerror(
397
+ APLOG_MARK,
398
+ APLOG_ERR,
399
+ rv,
400
+ r,
401
+ "xsendfile: unable to stat file: %s",
402
+ newpath
403
+ );
404
+ apr_file_close(fd);
405
+ ap_remove_output_filter(f);
406
+ ap_die(HTTP_FORBIDDEN, r);
407
+ return HTTP_FORBIDDEN;
408
+ }
409
+ /*
410
+ no inclusion of directories!
411
+ we're serving files!
412
+ */
413
+ if (finfo.filetype != APR_REG)
414
+ {
415
+ ap_log_rerror(
416
+ APLOG_MARK,
417
+ APLOG_ERR,
418
+ APR_EBADPATH,
419
+ r,
420
+ "xsendfile: not a file %s",
421
+ newpath
422
+ );
423
+ apr_file_close(fd);
424
+ ap_remove_output_filter(f);
425
+ ap_die(HTTP_NOT_FOUND, r);
426
+ return HTTP_NOT_FOUND;
427
+ }
428
+
429
+ /*
430
+ need to cheat here a bit
431
+ as etag generator will use those ;)
432
+ and we want local_copy and cache
433
+ */
434
+ r->finfo.inode = finfo.inode;
435
+ r->finfo.size = finfo.size;
436
+
437
+ /*
438
+ caching? why not :p
439
+ */
440
+ r->no_cache = r->no_local_copy = 0;
441
+
442
+ ap_update_mtime(r, finfo.mtime);
443
+ ap_set_last_modified(r);
444
+ ap_set_content_length(r, finfo.size);
445
+ ap_set_etag(r);
446
+
447
+ /* as we dropped all the content this field is not valid anymore! */
448
+ apr_table_unset(r->headers_out, "Content-Encoding");
449
+ apr_table_unset(r->err_headers_out, "Content-Encoding");
450
+
451
+ /* cache or something? */
452
+ if ((errcode = ap_meets_conditions(r)) != OK)
453
+ {
454
+ #ifdef _DEBUG
455
+ ap_log_error(
456
+ APLOG_MARK,
457
+ APLOG_DEBUG,
458
+ 0,
459
+ r->server,
460
+ "xsendfile: met condition %d for %s",
461
+ errcode,
462
+ file
463
+ );
464
+ #endif
465
+ apr_file_close(fd);
466
+ r->status = errcode;
467
+ }
468
+ else
469
+ {
470
+ e = apr_bucket_file_create(
471
+ fd,
472
+ 0,
473
+ (apr_size_t)finfo.size,
474
+ r->pool,
475
+ in->bucket_alloc
476
+ );
477
+ #if APR_HAS_MMAP
478
+ if (coreconf->enable_mmap == ENABLE_MMAP_ON)
479
+ {
480
+ apr_bucket_file_enable_mmap(e, 0);
481
+ }
482
+ #if defined(_DEBUG)
483
+ else
484
+ {
485
+ ap_log_error(
486
+ APLOG_MARK,
487
+ APLOG_WARNING,
488
+ 0,
489
+ r->server,
490
+ "xsendfile: mmap configured, but not active %d",
491
+ coreconf->enable_mmap
492
+ );
493
+ }
494
+ #endif /* _DEBUG */
495
+ #endif /* APR_HAS_MMAP */
496
+ APR_BRIGADE_INSERT_TAIL(in, e);
497
+ }
498
+
499
+ e = apr_bucket_eos_create(in->bucket_alloc);
500
+ APR_BRIGADE_INSERT_TAIL(in, e);
501
+
502
+ /* remove ourselves from the filter chain */
503
+ ap_remove_output_filter(f);
504
+
505
+ #ifdef _DEBUG
506
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: sending %d bytes", (int)finfo.size);
507
+ #endif
508
+
509
+ /* send the data up the stack */
510
+ return ap_pass_brigade(f->next, in);
511
+ }
512
+
513
+ static void ap_xsendfile_insert_output_filter(request_rec *r)
514
+ {
515
+ xsendfile_conf_active_t enabled = ((xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module))->enabled;
516
+ if (XSENDFILE_UNSET == enabled)
517
+ {
518
+ enabled = ((xsendfile_conf_t*)ap_get_module_config(r->server->module_config, &xsendfile_module))->enabled;
519
+ }
520
+
521
+ if (XSENDFILE_ENABLED != enabled)
522
+ {
523
+ return;
524
+ }
525
+
526
+ ap_add_output_filter(
527
+ "XSENDFILE",
528
+ NULL,
529
+ r,
530
+ r->connection
531
+ );
532
+ }
533
+ static const command_rec xsendfile_command_table[] = {
534
+ AP_INIT_FLAG(
535
+ "XSendFile",
536
+ xsendfile_cmd_flag,
537
+ NULL,
538
+ OR_OPTIONS,
539
+ "On|Off - Enable/disable(default) processing"
540
+ ),
541
+ AP_INIT_FLAG(
542
+ "XSendFileAllowAbove",
543
+ xsendfile_cmd_flag,
544
+ NULL,
545
+ OR_OPTIONS,
546
+ "On|Off - Allow/disallow(default) sending files above Request path"
547
+ ),
548
+ { NULL }
549
+ };
550
+ static void xsendfile_register_hooks(apr_pool_t *p)
551
+ {
552
+ ap_register_output_filter(
553
+ "XSENDFILE",
554
+ ap_xsendfile_output_filter,
555
+ NULL,
556
+ AP_FTYPE_CONTENT_SET
557
+ );
558
+
559
+ ap_hook_insert_filter(
560
+ ap_xsendfile_insert_output_filter,
561
+ NULL,
562
+ NULL,
563
+ APR_HOOK_LAST + 1
564
+ );
565
+ }
566
+ module AP_MODULE_DECLARE_DATA xsendfile_module =
567
+ {
568
+ STANDARD20_MODULE_STUFF,
569
+ xsendfile_config_perdir_create,
570
+ xsendfile_config_perdir_merge,
571
+ xsendfile_config_server_create,
572
+ xsendfile_config_server_merge,
573
+ xsendfile_command_table,
574
+ xsendfile_register_hooks
575
+ };
@@ -0,0 +1 @@
1
+ require 'rack/gem_assets'
@@ -0,0 +1,135 @@
1
+ require 'rubygems'
2
+
3
+ module Rack #:nodoc:
4
+ class GemAssets
5
+
6
+ F = ::File
7
+
8
+ # :assets_dir:: the sub directory where we will look for assets. (default: <tt>public</tt>)
9
+ # :xsendfile:: use XSendfile to send files.(default: <tt>true</tt>)
10
+ def initialize(app, options={})
11
+ @app = app
12
+ @options = { :assets_dir => 'public', :xsendfile => true }.merge(options)
13
+
14
+ @public_dirs = {}
15
+ @asset_paths = {}
16
+ @asset_sizes = {}
17
+ @asset_mimes = {}
18
+ end
19
+
20
+ def call(env)
21
+ unless env["REQUEST_METHOD"].to_s
22
+ return pass env
23
+ end
24
+
25
+ path = Rack::Utils.unescape(env["PATH_INFO"])
26
+ if path.include?("..") or path.include?("~")
27
+ return pass env
28
+ end
29
+
30
+ full_path = path_to_asset(path)
31
+ if full_path
32
+ return send_file full_path
33
+ end
34
+
35
+ return pass env
36
+ end
37
+
38
+ private
39
+
40
+ def pass(env)
41
+ @app.call(env)
42
+ end
43
+
44
+ def send_file(full_path)
45
+ if @options[:xsendfile]
46
+ [200, {
47
+ 'X-Sendfile' => full_path,
48
+ 'Content-length' => size_for_asset(full_path).to_s,
49
+ 'Content-Type' => mime_for_asset(full_path)
50
+ }, []]
51
+ else
52
+ [200, {
53
+ 'Content-length' => size_for_asset(full_path).to_s,
54
+ 'Content-Type' => mime_for_asset(full_path)
55
+ }, [F.read(full_path)]]
56
+ end
57
+ end
58
+
59
+ def size_for_asset(path)
60
+ @asset_sizes[path] ||= F.size(path)
61
+ end
62
+
63
+ def mime_for_asset(path)
64
+ @asset_mimes[path] ||= Rack::Mime.mime_type(F.extname(path))
65
+ end
66
+
67
+ def path_to_asset(path)
68
+ return @asset_paths[path] if @asset_paths.key?(path)
69
+
70
+ full_path = nil
71
+
72
+ if path =~ %r{^/vendor/([^/]+)/(.+)$}
73
+ full_path = path_to_asset_for_gem($2, $1)
74
+ if full_path
75
+ @asset_paths[path] = full_path
76
+ return full_path
77
+ end
78
+ end
79
+
80
+ Gem::loaded_specs.keys.each do |gem_name|
81
+ full_path = path_to_asset_for_gem(path, gem_name)
82
+ if full_path
83
+ @asset_paths[path] = full_path
84
+ return full_path
85
+ end
86
+ end
87
+
88
+ @asset_paths[path] = full_path
89
+ return full_path
90
+ end
91
+
92
+ def path_to_asset_for_gem(path, gem_name)
93
+ public_dir = public_dir_for_gem(gem_name)
94
+ return nil unless public_dir
95
+
96
+ full_path = F.join(public_dir, path)
97
+
98
+ if full_path[-1,1] == '/'
99
+ full_path = full_path[0..-2]
100
+ end
101
+
102
+ if F.file?(full_path) and F.readable?(full_path)
103
+ return full_path
104
+ end
105
+
106
+ html_path = full_path+".html"
107
+ if F.file?(html_path) and F.readable?(html_path)
108
+ return html_path
109
+ end
110
+
111
+ index_path = full_path+"/index.html"
112
+ if F.file?(index_path) and F.readable?(index_path)
113
+ return index_path
114
+ end
115
+
116
+ return nil
117
+ end
118
+
119
+ def public_dir_for_gem(gem_name)
120
+ return @public_dirs[gem_name] if @public_dirs.key?(gem_name)
121
+
122
+ spec = Gem::loaded_specs[gem_name]
123
+ if spec
124
+ public_dir = F.join(spec.full_gem_path, @options[:assets_dir])
125
+ if F.directory?(public_dir)
126
+ @public_dirs[gem_name] = public_dir
127
+ return public_dir
128
+ end
129
+ end
130
+
131
+ return nil
132
+ end
133
+
134
+ end
135
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'rack-gem-assets'
7
+
8
+ class Test::Unit::TestCase
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestRackGemAssets < Test::Unit::TestCase
4
+ def test_something_for_real
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-gem-assets
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Simon Menke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-09 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A Middleware for sending assets packaged in loaded gems
17
+ email: simon@mrhenry.be
18
+ engine_dependencies: {}
19
+
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files:
25
+ - LICENSE
26
+ - README.rdoc
27
+ files:
28
+ - .document
29
+ - .gitignore
30
+ - LICENSE
31
+ - README.rdoc
32
+ - Rakefile
33
+ - VERSION
34
+ - dist/apache/mod_xsendfile.c
35
+ - lib/rack-gem-assets.rb
36
+ - lib/rack/gem_assets.rb
37
+ - test/helper.rb
38
+ - test/test_rack-gem-assets.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/simonmenke/rack-gem-assets
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: See description
67
+ test_files:
68
+ - test/helper.rb
69
+ - test/test_rack-gem-assets.rb