leifcr-refile 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/javascripts/refile.js +125 -0
  3. data/config/locales/en.yml +10 -0
  4. data/config/routes.rb +5 -0
  5. data/lib/refile.rb +510 -0
  6. data/lib/refile/app.rb +186 -0
  7. data/lib/refile/attacher.rb +190 -0
  8. data/lib/refile/attachment.rb +108 -0
  9. data/lib/refile/attachment/active_record.rb +133 -0
  10. data/lib/refile/attachment_definition.rb +83 -0
  11. data/lib/refile/backend/file_system.rb +120 -0
  12. data/lib/refile/backend/s3.rb +1 -0
  13. data/lib/refile/backend_macros.rb +45 -0
  14. data/lib/refile/custom_logger.rb +48 -0
  15. data/lib/refile/file.rb +102 -0
  16. data/lib/refile/file_double.rb +13 -0
  17. data/lib/refile/image_processing.rb +1 -0
  18. data/lib/refile/rails.rb +54 -0
  19. data/lib/refile/rails/attachment_helper.rb +121 -0
  20. data/lib/refile/random_hasher.rb +11 -0
  21. data/lib/refile/signature.rb +36 -0
  22. data/lib/refile/simple_form.rb +17 -0
  23. data/lib/refile/type.rb +28 -0
  24. data/lib/refile/version.rb +3 -0
  25. data/spec/refile/active_record_helper.rb +35 -0
  26. data/spec/refile/app_spec.rb +424 -0
  27. data/spec/refile/attachment/active_record_spec.rb +568 -0
  28. data/spec/refile/attachment_helper_spec.rb +78 -0
  29. data/spec/refile/attachment_spec.rb +589 -0
  30. data/spec/refile/backend/file_system_spec.rb +5 -0
  31. data/spec/refile/backend_examples.rb +228 -0
  32. data/spec/refile/backend_macros_spec.rb +83 -0
  33. data/spec/refile/custom_logger_spec.rb +21 -0
  34. data/spec/refile/features/direct_upload_spec.rb +63 -0
  35. data/spec/refile/features/multiple_upload_spec.rb +122 -0
  36. data/spec/refile/features/normal_upload_spec.rb +144 -0
  37. data/spec/refile/features/presigned_upload_spec.rb +31 -0
  38. data/spec/refile/features/simple_form_spec.rb +8 -0
  39. data/spec/refile/fixtures/hello.txt +1 -0
  40. data/spec/refile/fixtures/image.jpg +0 -0
  41. data/spec/refile/fixtures/large.txt +44 -0
  42. data/spec/refile/fixtures/monkey.txt +1 -0
  43. data/spec/refile/fixtures/world.txt +1 -0
  44. data/spec/refile/spec_helper.rb +72 -0
  45. data/spec/refile_spec.rb +355 -0
  46. metadata +143 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f781b9f6d4404dc29d2b92347798a84e7208a8ad
4
+ data.tar.gz: d3d68577da41b921cfa349aafc142bb8d5d5550e
5
+ SHA512:
6
+ metadata.gz: d605f5943011f2b8258b335b045f949f58aabf4eb66a4f1e2f3001387d682d3c60f0580543e22b8d426695b959def093858a1cb904a2915b98277c90f0d80748
7
+ data.tar.gz: 55e9254dc55bfccf60caa620bf1afe3257b4f630e9d459c826bc1ce88e0fecf137fa721bf13fcddc53da8a1223781648d0dd8dcdfd5b2359c5f8c2e3c3749b2e
@@ -0,0 +1,125 @@
1
+ (function() {
2
+ "use strict";
3
+
4
+ function isSuccess(xhr) {
5
+ return (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304
6
+ }
7
+
8
+ function formData(as, file, fields) {
9
+ var data = new FormData();
10
+
11
+ if(fields) {
12
+ Object.keys(fields).forEach(function(key) {
13
+ data.append(key, fields[key]);
14
+ });
15
+ }
16
+
17
+ data.append(as, file);
18
+
19
+ return data;
20
+ }
21
+
22
+ if(!document.addEventListener) { return; } // IE8
23
+
24
+ document.addEventListener("change", function(changeEvent) {
25
+ var input = changeEvent.target;
26
+ if(input.tagName === "INPUT" && input.type === "file" && input.getAttribute("data-direct")) {
27
+ if(!input.files) { return; } // IE9, bail out if file API is not supported.
28
+
29
+ var reference = input.getAttribute("data-reference");
30
+ var metadataField = document.querySelector("input[type=hidden][data-reference='" + reference + "']");
31
+
32
+ var url = input.getAttribute("data-url");
33
+ var fields = JSON.parse(input.getAttribute("data-fields") || "null");
34
+
35
+ var requests = [].map.call(input.files, function(file, index) {
36
+ function dispatchEvent(element, name, progress) {
37
+ var ev = document.createEvent('CustomEvent');
38
+ ev.initCustomEvent(name, true, false, { xhr: xhr, file: file, index: index, progress: progress });
39
+ element.dispatchEvent(ev);
40
+ }
41
+
42
+ var xhr = new XMLHttpRequest();
43
+
44
+ xhr.file = file;
45
+
46
+ xhr.addEventListener("load", function() {
47
+ xhr.complete = true;
48
+ if(requests.every(function(xhr) { return xhr.complete })) {
49
+ finalizeUpload();
50
+ }
51
+ if(isSuccess(xhr)) {
52
+ dispatchEvent(input, "upload:success");
53
+ } else {
54
+ dispatchEvent(input, "upload:failure");
55
+ }
56
+ dispatchEvent(input, "upload:complete");
57
+ });
58
+
59
+ xhr.upload.addEventListener("progress", function(progressEvent) {
60
+ dispatchEvent(input, "upload:progress", progressEvent);
61
+ });
62
+
63
+ if(input.getAttribute("data-presigned")) {
64
+ dispatchEvent(input, "presign:start");
65
+ var presignXhr = new XMLHttpRequest();
66
+ var presignUrl = url + "?t=" + Date.now() + "." + index;
67
+ presignXhr.addEventListener("load", function() {
68
+ dispatchEvent(input, "presign:complete");
69
+ if(isSuccess(presignXhr)) {
70
+ dispatchEvent(input, "presign:success");
71
+ var data = JSON.parse(presignXhr.responseText)
72
+ xhr.id = data.id;
73
+ xhr.open("POST", data.url, true);
74
+ xhr.send(formData(data.as, file, data.fields));
75
+ dispatchEvent(input, "upload:start");
76
+ } else {
77
+ dispatchEvent(input, "presign:failure");
78
+ xhr.complete = true;
79
+ };
80
+ });
81
+ presignXhr.open("GET", presignUrl, true);
82
+ presignXhr.send();
83
+ } else {
84
+ xhr.open("POST", url, true);
85
+ xhr.send(formData(input.getAttribute("data-as"), file, fields));
86
+ dispatchEvent(input, "upload:start");
87
+ }
88
+
89
+ return xhr;
90
+ });
91
+
92
+ if(requests.length) {
93
+ input.classList.add("uploading");
94
+ }
95
+
96
+ var finalizeUpload = function() {
97
+ input.classList.remove("uploading");
98
+
99
+ if(requests.every(isSuccess)) {
100
+ var dataObj = requests.map(function(xhr) {
101
+ var json, data = { filename: xhr.file.name, content_type: xhr.file.type, size: xhr.file.size };
102
+
103
+ try {
104
+ json = JSON.parse(xhr.responseText);
105
+ } catch(e) {
106
+ json = {};
107
+ }
108
+
109
+ var id = xhr.id || json.id;
110
+ var url = xhr.url || json.url;
111
+
112
+ if (id) data.id = id;
113
+ if (url) data.url = url;
114
+
115
+ return data;
116
+ });
117
+ if(!input.multiple) dataObj = dataObj[0];
118
+ if(metadataField) metadataField.value = JSON.stringify(dataObj);
119
+
120
+ input.removeAttribute("name");
121
+ }
122
+ }
123
+ }
124
+ });
125
+ })();
@@ -0,0 +1,10 @@
1
+ en:
2
+ activerecord:
3
+ errors:
4
+ messages:
5
+ too_large: "is too large"
6
+ download_failed: "could not be downloaded"
7
+ invalid_content_type: "You are not allowed to upload %{content} file format. Allowed types: %{permitted}."
8
+ invalid_extension: "You are not allowed to upload %{extension} file extension. Allowed types: %{permitted}."
9
+ refile:
10
+ empty_param: "an empty"
@@ -0,0 +1,5 @@
1
+ if Refile.automount
2
+ Rails.application.routes.draw do
3
+ mount Refile.app, at: Refile.mount_point, as: :refile_app
4
+ end
5
+ end
@@ -0,0 +1,510 @@
1
+ require "uri"
2
+ require "fileutils"
3
+ require "tempfile"
4
+ require "rest_client"
5
+ require "logger"
6
+ require "mime/types"
7
+
8
+ module Refile
9
+ # @api private
10
+ class Invalid < StandardError; end
11
+
12
+ # @api private
13
+ class InvalidID < Invalid; end
14
+
15
+ # @api private
16
+ class InvalidMaxSize < Invalid; end
17
+
18
+ # @api private
19
+ class InvalidFile < Invalid; end
20
+
21
+ # @api private
22
+ class Confirm < StandardError
23
+ def message
24
+ "are you sure? this will remove all files in the backend, call as \
25
+ `clear!(:confirm)` if you're sure you want to do this"
26
+ end
27
+ end
28
+
29
+ class << self
30
+ # A shortcut to the instance of the Rack application. This should be
31
+ # set when the application is initialized. `refile/rails` sets this
32
+ # value.
33
+ #
34
+ # @return [Refile::App, nil]
35
+ attr_accessor :app
36
+
37
+ # The host name of a CDN distribution that the Rack application can be
38
+ # reached at. If not set, Refile will use an absolute URL without hostname.
39
+ # It is strongly recommended to run Refile behind a CDN and to set this to
40
+ # the hostname of the CDN distribution.
41
+ #
42
+ # The `cdn_host` setting is used when retrieving files, but not when
43
+ # uploading new files, since uploads should normally not go through the
44
+ # CDN.
45
+ #
46
+ # A protocol relative URL is recommended for this value.
47
+ #
48
+ # @return [String, nil]
49
+ attr_accessor :cdn_host
50
+
51
+ # The host name that the Rack application can be reached at. If not set,
52
+ # Refile will use an absolute URL without hostname. You should only change
53
+ # this setting if you are running the Refile app on a different domain
54
+ # than your main application.
55
+ #
56
+ # If you are simply running the Refile app behind a CDN you'll want to
57
+ # change {Refile.cdn_host} instead.
58
+ #
59
+ # The difference between {Refile.app_host} and {Refile.cdn_host} is that the
60
+ # latter only affects URLs generated by {Refile.file_url} and the
61
+ # {Refile::AttachmentHelper#attachment_url} and
62
+ # {Refile::AttachmentHelper#attachment_image_tag} helpers, whereas the
63
+ # former also affects {Refile.upload_url}, {Refile.presign_url} and the
64
+ # {Refile::AttachmentHelper#attachment_field} helper.
65
+ #
66
+ # @return [String, nil]
67
+ attr_accessor :app_host
68
+
69
+ # @deprecated use {Refile.cdn_host} instead
70
+ def host
71
+ warn "Refile.host is deprecated, please use Refile.cdn_host instead"
72
+ cdn_host
73
+ end
74
+
75
+ # @deprecated use {Refile.cdn_host} instead
76
+ def host=(host)
77
+ warn "Refile.host is deprecated, please use Refile.cdn_host instead"
78
+ self.cdn_host = host
79
+ end
80
+
81
+ # A list of names which identify backends in the global backend registry.
82
+ # The Rack application allows POST requests to only the backends specified
83
+ # in this config option. This defaults to `["cache"]`, only allowing direct
84
+ # uploads to the cache backend.
85
+ #
86
+ # @return [Array[String], :all]
87
+ attr_accessor :allow_uploads_to
88
+
89
+ # A list of names which identify backends in the global backend registry.
90
+ # The Rack application allows GET requests to only the backends specified
91
+ # in this config option. This defaults to `:all`, allowing files from all
92
+ # backends to be downloaded.
93
+ #
94
+ # @return [Array[String], :all]
95
+ attr_accessor :allow_downloads_from
96
+
97
+ # Logger that should be used by rack application
98
+ #
99
+ # @return [Logger]
100
+ attr_accessor :logger
101
+
102
+ # Value for Access-Control-Allow-Origin header
103
+ #
104
+ # @return [String]
105
+ attr_accessor :allow_origin
106
+
107
+ # Value for Cache-Control: max-age=<value in seconds> header
108
+ #
109
+ # @return [Integer]
110
+ attr_accessor :content_max_age
111
+
112
+ # Where should the rack application be mounted? The default is 'attachments'.
113
+ #
114
+ # @return [String]
115
+ attr_accessor :mount_point
116
+
117
+ # Should the rack application be automounted in a Rails app?
118
+ #
119
+ # If set to false then Refile.app should be mounted in the Rails application
120
+ # routes.rb with the options `at: Refile.mount_point, as: :refile_app`
121
+ #
122
+ # The default is true.
123
+ #
124
+ # @return [Boolean]
125
+ attr_accessor :automount
126
+
127
+ # Value for generating signed attachment urls to protect from DoS
128
+ #
129
+ # @return [String]
130
+ attr_accessor :secret_key
131
+
132
+ # A global registry of backends.
133
+ #
134
+ # @return [Hash{String => Backend}]
135
+ def backends
136
+ @backends ||= {}
137
+ end
138
+
139
+ # A global registry of processors. These will be used by the Rack
140
+ # application to manipulate files prior to serving them up to the user,
141
+ # based on options sent trough the URL. This can be used for example to
142
+ # resize images or to convert files to another file format.
143
+ #
144
+ # @return [Hash{String => Proc}]
145
+ def processors
146
+ @processors ||= {}
147
+ end
148
+
149
+ # A global registry of types. Currently, types are simply aliases for a set
150
+ # of content types, but their functionality may expand in the future.
151
+ #
152
+ # @return [Hash{Symbol => Refile::Type}]
153
+ def types
154
+ @types ||= {}
155
+ end
156
+
157
+ # Adds a processor. The processor must respond to `call`, both receiving
158
+ # and returning an IO-like object. Alternatively a block can be given to
159
+ # this method which also receives and returns an IO-like object.
160
+ #
161
+ # An IO-like object is recommended to be an instance of the `IO` class or
162
+ # one of its subclasses, like `File` or a `StringIO`, or a `Refile::File`.
163
+ # It can also be any other object which responds to `size`, `read`, `eof`?
164
+ # `rewind` and `close` and mimics the behaviour of IO objects for these
165
+ # methods.
166
+ #
167
+ # @example With processor class
168
+ # class Reverse
169
+ # def call(file)
170
+ # StringIO.new(file.read.reverse)
171
+ # en
172
+ # end
173
+ # Refile.processor(:reverse, Reverse)
174
+ #
175
+ # @example With block
176
+ # Refile.processor(:reverse) do |file|
177
+ # StringIO.new(file.read.reverse)
178
+ # end
179
+ #
180
+ # @param [#to_s] name The name of the processor
181
+ # @param [Proc, nil] processor The processor, must respond to `call` and.
182
+ # @yield [Refile::File] The file to modify
183
+ # @yieldreturn [IO] An IO-like object representing the processed file
184
+ # @return [void]
185
+ def processor(name, processor = nil, &block)
186
+ processor ||= block
187
+ processors[name.to_s] = processor
188
+ end
189
+
190
+ # A shortcut to retrieving the backend named "store" from the global
191
+ # registry.
192
+ #
193
+ # @return [Backend]
194
+ def store
195
+ backends["store"]
196
+ end
197
+
198
+ # A shortcut to setting the backend named "store" in the global registry.
199
+ #
200
+ # @param [Backend] backend
201
+ def store=(backend)
202
+ backends["store"] = backend
203
+ end
204
+
205
+ # A shortcut to retrieving the backend named "cache" from the global
206
+ # registry.
207
+ #
208
+ # @return [Backend]
209
+ def cache
210
+ backends["cache"]
211
+ end
212
+
213
+ # A shortcut to setting the backend named "cache" in the global registry.
214
+ #
215
+ # @param [Backend] backend
216
+ def cache=(backend)
217
+ backends["cache"] = backend
218
+ end
219
+
220
+ # Yield the Refile module as a convenience for configuring multiple
221
+ # config options at once.
222
+ #
223
+ # @yield Refile
224
+ def configure
225
+ yield self
226
+ end
227
+
228
+ # Extract the filename from an uploadable object. If the filename cannot be
229
+ # determined, this method will return `nil`.
230
+ #
231
+ # @param [IO] uploadable The uploadable object to extract the filename from
232
+ # @return [String, nil] The extracted filename
233
+ def extract_filename(uploadable)
234
+ path = if uploadable.respond_to?(:original_filename)
235
+ uploadable.original_filename
236
+ elsif uploadable.respond_to?(:path)
237
+ uploadable.path
238
+ end
239
+ ::File.basename(path) if path
240
+ end
241
+
242
+ # Extract the content type from an uploadable object. If the content type
243
+ # cannot be determined, this method will return `nil`.
244
+ #
245
+ # @param [IO] uploadable The uploadable object to extract the content type from
246
+ # @return [String, nil] The extracted content type
247
+ def extract_content_type(uploadable)
248
+ if uploadable.respond_to?(:content_type)
249
+ uploadable.content_type
250
+ else
251
+ filename = extract_filename(uploadable)
252
+ if filename
253
+ content_type = MIME::Types.of(filename).first
254
+ content_type.to_s if content_type
255
+ end
256
+ end
257
+ end
258
+
259
+ # Generates a URL to the Refile application.
260
+ #
261
+ # The host defaults to {Refile.app_host}. You can also override the host via
262
+ # the `host` option. Normally the Refile app will not be mounted at the
263
+ # root but rather at some other path, the `prefix` option allows you to
264
+ # override this setting, and if not set it will fall back to
265
+ # {Refile.mount_point}.
266
+ #
267
+ # @example
268
+ # Refile.app_url
269
+ #
270
+ # @example With host and prefix
271
+ # Refile.app_url(host: "http://some.domain", prefix: "/refile")
272
+ #
273
+ # @param [String, nil] host Override the host
274
+ # @param [String, nil] prefix Adds a prefix to the URL if the application is not mounted at root
275
+ # @return [String] The generated URL
276
+ def app_url(host: nil, prefix: nil)
277
+ host ||= Refile.app_host
278
+ prefix ||= Refile.mount_point
279
+
280
+ uri = URI(host.to_s)
281
+ uri.path = prefix || "/"
282
+ uri.to_s
283
+ end
284
+
285
+ # Receives a {Refile::File} and generates a URL to it.
286
+ #
287
+ # Optionally the name of a processor and arguments to it can be appended.
288
+ #
289
+ # The `filename` option must be given.
290
+ #
291
+ # The host defaults to {Refile.cdn_host}, which is useful for serving all
292
+ # attachments from a CDN. You can also override the host via the `host`
293
+ # option.
294
+ #
295
+ # Returns `nil` if the supplied file is `nil`.
296
+ #
297
+ # @example
298
+ # Refile.file_url(Refile.store.get(id))
299
+ #
300
+ # @example With processor
301
+ # Refile.file_url(Refile.store.get(id), :image, :fill, 300, 300, format: "jpg")
302
+ #
303
+ # @param [Refile::File] file The file to generate a URL for
304
+ # @param [String] filename The filename to be appended to the URL
305
+ # @param [String, nil] format A file extension to be appended to the URL
306
+ # @param [String, nil] host Override the host
307
+ # @param [String, nil] prefix Adds a prefix to the URL if the application is not mounted at root
308
+ # @param [String, nil] expires_at Adds a sulfix to the URL that sets the expiration time of the URL
309
+ # @param [String, nil] force_download Adds a sulfix to the URL to force the download of the file when URL is accessed
310
+ # @return [String, nil] The generated URL
311
+ def file_url(file, *args, expires_at: nil, host: nil, prefix: nil, filename:, format: nil, force_download: nil)
312
+ return unless file
313
+
314
+ host ||= Refile.cdn_host
315
+ backend_name = Refile.backends.key(file.backend)
316
+
317
+ filename = Rack::Utils.escape(filename)
318
+ filename << "." << format.to_s if format && !filename.downcase.end_with?(format.to_s.downcase)
319
+
320
+ base_path = ::File.join("", backend_name, *args.map(&:to_s), file.id.to_s, filename)
321
+ if expires_at
322
+ base_path += "?expires_at=#{expires_at.to_i}" # UNIX timestamp
323
+ end
324
+
325
+ base_path += "?force_download=true" if force_download
326
+
327
+ ::File.join(app_url(prefix: prefix, host: host), token(base_path), base_path)
328
+ end
329
+
330
+ # Receives a Refile backend and returns a URL to the Refile application
331
+ # where files can be uploaded.
332
+ #
333
+ # @example
334
+ # Refile.upload_url(Refile.store)
335
+ #
336
+ # @param [Refile::Backend] backend The backend to generate a URL for
337
+ # @param [String, nil] host Override the host
338
+ # @param [String, nil] prefix Adds a prefix to the URL if the application is not mounted at root
339
+ # @return [String] The generated URL
340
+ def upload_url(backend, host: nil, prefix: nil)
341
+ backend_name = Refile.backends.key(backend)
342
+
343
+ ::File.join(app_url(host: host, prefix: prefix), backend_name)
344
+ end
345
+
346
+ # Receives a Refile backend and returns a URL to the Refile application
347
+ # where a presign object for the backend can be retrieved.
348
+ #
349
+ # @example
350
+ # Refile.upload_url(Refile.store)
351
+ #
352
+ # @param [Refile::Backend] backend The backend to generate a URL for
353
+ # @param [String, nil] host Override the host
354
+ # @param [String, nil] prefix Adds a prefix to the URL if the application is not mounted at root
355
+ # @return [String] The generated URL
356
+ def presign_url(backend, host: nil, prefix: nil)
357
+ ::File.join(upload_url(backend, host: host, prefix: prefix), "presign")
358
+ end
359
+
360
+ # Generate a URL to an attachment. Receives an instance of a class which
361
+ # has used the {Refile::Attachment#attachment} macro to generate an
362
+ # attachment column, and the name of this column, and based on this
363
+ # generates a URL to a {Refile::App}.
364
+ #
365
+ # Optionally the name of a processor and arguments to it can be appended.
366
+ #
367
+ # If the filename option is not given, the filename is taken from the
368
+ # metadata stored in the attachment, or eventually falls back to the
369
+ # `name`.
370
+ #
371
+ # The host defaults to {Refile.cdn_host}, which is useful for serving all
372
+ # attachments from a CDN. You can also override the host via the `host`
373
+ # option.
374
+ #
375
+ # Returns `nil` if there is no file attached.
376
+ #
377
+ # @example
378
+ # Refile.attachment_url(@post, :document)
379
+ #
380
+ # @example With processor
381
+ # Refile.attachment_url(@post, :image, :fill, 300, 300, format: "jpg")
382
+ #
383
+ # @param [Refile::Attachment] object Instance of a class which has an attached file
384
+ # @param [Symbol] name The name of the attachment column
385
+ # @param [String, nil] filename The filename to be appended to the URL
386
+ # @param [String, nil] format A file extension to be appended to the URL
387
+ # @param [String, nil] host Override the host
388
+ # @param [String, nil] prefix Adds a prefix to the URL if the application is not mounted at root
389
+ # @param [String, nil] expires_at Adds a sulfix to the URL that sets the expiration time of the URL
390
+ # @param [String, nil] force_download Adds a sulfix to the URL to force the download of the file when URL is accessed
391
+ # @return [String, nil] The generated URL
392
+ def attachment_url(object, name, *args, expires_at: nil, host: nil, prefix: nil, filename: nil, format: nil, force_download: nil)
393
+ attacher = object.send(:"#{name}_attacher")
394
+ file = attacher.get
395
+ return unless file
396
+
397
+ filename ||= attacher.basename || name.to_s
398
+ format ||= attacher.extension
399
+
400
+ file_url(file, *args, expires_at: expires_at, host: host, prefix: prefix, filename: filename, format: format, force_download: force_download)
401
+ end
402
+
403
+ # Receives an instance of a class which has used the
404
+ # {Refile::Attachment#attachment} macro to generate an attachment column,
405
+ # and the name of this column, and based on this generates a URL to a
406
+ # {Refile::App} where files can be uploaded.
407
+ #
408
+ # @example
409
+ # Refile.attachment_upload_url(@post, :document)
410
+ #
411
+ # @param [Refile::Attachment] object Instance of a class which has an attached file
412
+ # @param [Symbol] name The name of the attachment column
413
+ # @param [String, nil] host Override the host
414
+ # @param [String, nil] prefix Adds a prefix to the URL if the application is not mounted at root
415
+ # @return [String] The generated URL
416
+ def attachment_upload_url(object, name, host: nil, prefix: nil)
417
+ backend = object.send(:"#{name}_attachment_definition").cache
418
+
419
+ upload_url(backend, host: host, prefix: prefix)
420
+ end
421
+
422
+ # Receives an instance of a class which has used the
423
+ # {Refile::Attachment#attachment} macro to generate an attachment column,
424
+ # and the name of this column, and based on this generates a URL to a
425
+ # {Refile::App} where a presign object for the backend can be retrieved.
426
+ #
427
+ # @example
428
+ # Refile.attachment_presign_url(@post, :document)
429
+ #
430
+ # @param [Refile::Attachment] object Instance of a class which has an attached file
431
+ # @param [Symbol] name The name of the attachment column
432
+ # @param [String, nil] host Override the host
433
+ # @param [String, nil] prefix Adds a prefix to the URL if the application is not mounted at root
434
+ # @return [String] The generated URL
435
+ def attachment_presign_url(object, name, host: nil, prefix: nil)
436
+ backend = object.send(:"#{name}_attachment_definition").cache
437
+
438
+ presign_url(backend, host: host, prefix: prefix)
439
+ end
440
+
441
+ # Generate a signature for a given path concatenated with the configured secret token.
442
+ #
443
+ # Raises an error if no secret token is configured.
444
+ #
445
+ # @example
446
+ # Refile.token('/store/f5f2e4/document.pdf')
447
+ #
448
+ # @param [String] path The path to generate a token for
449
+ # @raise [RuntimeError] If {Refile.secret_key} is not set
450
+ # @return [String, nil] The generated token
451
+ def token(path)
452
+ if secret_key.nil?
453
+ error = "Refile.secret_key was not set.\n\n"
454
+ error << "Please add the following to your Refile configuration and restart your application:\n\n"
455
+ error << "```\nRefile.secret_key = '#{SecureRandom.hex(64)}'\n```\n\n"
456
+
457
+ raise error
458
+ end
459
+
460
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha1"), secret_key, path)
461
+ end
462
+
463
+ # Check if the given token is a valid token for the given path.
464
+ #
465
+ # @example
466
+ # Refile.valid_token?('/store/f5f2e4/document.pdf', 'abcd1234')
467
+ #
468
+ # @param [String] path The path to check validity for
469
+ # @param [String] token The token to check
470
+ # @raise [RuntimeError] If {Refile.secret_key} is not set
471
+ # @return [Boolean] Whether the token is valid
472
+ def valid_token?(path, token)
473
+ expected = Digest::SHA1.hexdigest(token(path))
474
+ actual = Digest::SHA1.hexdigest(token)
475
+
476
+ expected == actual
477
+ end
478
+
479
+ # @api private
480
+ def parse_json(data, *args)
481
+ JSON.parse(data.to_s, *args)
482
+ rescue JSON::ParserError
483
+ nil
484
+ end
485
+ end
486
+
487
+ require "refile/version"
488
+ require "refile/signature"
489
+ require "refile/type"
490
+ require "refile/backend_macros"
491
+ require "refile/attachment_definition"
492
+ require "refile/attacher"
493
+ require "refile/attachment"
494
+ require "refile/random_hasher"
495
+ require "refile/file"
496
+ require "refile/custom_logger"
497
+ require "refile/app"
498
+ require "refile/backend/file_system"
499
+ end
500
+
501
+ Refile.configure do |config|
502
+ config.allow_uploads_to = ["cache"]
503
+ config.allow_downloads_from = :all
504
+ config.allow_origin = "*"
505
+ config.logger = Logger.new(STDOUT) unless ENV["RACK_ENV"] == "test"
506
+ config.mount_point = "/attachments"
507
+ config.automount = true
508
+ config.content_max_age = 60 * 60 * 24 * 365
509
+ config.types[:image] = Refile::Type.new(:image, content_type: %w[image/jpeg image/gif image/png])
510
+ end