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 +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
|