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 +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +48 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/dist/apache/mod_xsendfile.c +575 -0
- data/lib/rack-gem-assets.rb +1 -0
- data/lib/rack/gem_assets.rb +135 -0
- data/test/helper.rb +9 -0
- data/test/test_rack-gem-assets.rb +7 -0
- metadata +69 -0
data/.document
ADDED
data/.gitignore
ADDED
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
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
|