1 /*
2 * Copyright (c) 2002-2026 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://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 package org.htmlunit;
16
17 import java.util.Map;
18 import java.util.concurrent.ConcurrentHashMap;
19
20 import org.htmlunit.javascript.host.file.Blob;
21
22 /**
23 * The user-agent-wide blob URL store defined by
24 * <a href="https://w3c.github.io/FileAPI/#BlobURLStore">Blob URL Store</a>.
25 *
26 * @author Lai Quang Duong
27 * @author Ronald Brill
28 */
29 public class BlobUrlStore {
30
31 private static final class Entry {
32 private final Blob object_;
33 private final Page owningPage_;
34
35 Entry(final Blob object, final Page owningPage) {
36 object_ = object;
37 owningPage_ = owningPage;
38 }
39 }
40
41 private final Map<String, Entry> store_ = new ConcurrentHashMap<>();
42
43 /**
44 * Adds an entry.
45 *
46 * @param blobUrl the generated {@code blob:} URL
47 * @param object the {@link Blob} (or {@code File}) the URL refers to
48 * @param owningPage the page that created the URL
49 */
50 public void put(final String blobUrl, final Blob object, final Page owningPage) {
51 store_.put(blobUrl, new Entry(object, owningPage));
52 }
53
54 /**
55 * <a href="https://w3c.github.io/FileAPI/#blob-url-resolve">Resolve a blob URL</a>.
56 *
57 * @param blobUrl the {@code blob:} URL to resolve
58 * @return the stored {@link Blob}, or {@code null} if there is no entry
59 */
60 public Blob resolve(final String blobUrl) {
61 final Entry entry = store_.get(excludeFragment(blobUrl));
62 return entry == null ? null : entry.object_;
63 }
64
65 /**
66 * Removes an entry.
67 *
68 * @param blobUrl the {@code blob:} URL to remove
69 */
70 public void remove(final String blobUrl) {
71 store_.remove(excludeFragment(blobUrl));
72 }
73
74 /**
75 * See <a href="https://w3c.github.io/FileAPI/#lifeTime">Lifetime of blob URLs</a>.
76 *
77 * @param owningPage the unloading page
78 */
79 void removeForPage(final Page owningPage) {
80 store_.values().removeIf(entry -> entry.owningPage_ == owningPage);
81 }
82
83 /**
84 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
85 *
86 * Removes all entries.
87 */
88 public void clear() {
89 store_.clear();
90 }
91
92 private static String excludeFragment(final String blobUrl) {
93 final int fragmentIndex = blobUrl.indexOf('#');
94 return fragmentIndex >= 0 ? blobUrl.substring(0, fragmentIndex) : blobUrl;
95 }
96 }