rack-gem-assets 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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