1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "nsCOMPtr.h"
6 #include "nsIInputStream.h"
7 #include "nsNetUtil.h"
8 #include "nsIFileURL.h"
9 #include "nsIJARURI.h"
10 #include "nsIResProtocolHandler.h"
11 #include "nsIChromeRegistry.h"
12 #include "nsStringStream.h"
13 #include "StartupCacheUtils.h"
14 #include "mozilla/scache/StartupCache.h"
15 #include "mozilla/Omnijar.h"
16 
17 namespace mozilla {
18 namespace scache {
19 
NewObjectInputStreamFromBuffer(const char * buffer,uint32_t len,nsIObjectInputStream ** stream)20 nsresult NewObjectInputStreamFromBuffer(const char* buffer, uint32_t len,
21                                         nsIObjectInputStream** stream) {
22   nsCOMPtr<nsIInputStream> stringStream;
23   nsresult rv =
24       NS_NewByteInputStream(getter_AddRefs(stringStream), MakeSpan(buffer, len),
25                             NS_ASSIGNMENT_DEPEND);
26   MOZ_ALWAYS_SUCCEEDS(rv);
27 
28   nsCOMPtr<nsIObjectInputStream> objectInput =
29       NS_NewObjectInputStream(stringStream);
30 
31   objectInput.forget(stream);
32   return NS_OK;
33 }
34 
NewObjectOutputWrappedStorageStream(nsIObjectOutputStream ** wrapperStream,nsIStorageStream ** stream,bool wantDebugStream)35 nsresult NewObjectOutputWrappedStorageStream(
36     nsIObjectOutputStream** wrapperStream, nsIStorageStream** stream,
37     bool wantDebugStream) {
38   nsCOMPtr<nsIStorageStream> storageStream;
39 
40   nsresult rv =
41       NS_NewStorageStream(256, UINT32_MAX, getter_AddRefs(storageStream));
42   NS_ENSURE_SUCCESS(rv, rv);
43 
44   nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(storageStream);
45 
46   nsCOMPtr<nsIObjectOutputStream> objectOutput =
47       NS_NewObjectOutputStream(outputStream);
48 
49 #ifdef DEBUG
50   if (wantDebugStream) {
51     // Wrap in debug stream to detect unsupported writes of
52     // multiply-referenced non-singleton objects
53     StartupCache* sc = StartupCache::GetSingleton();
54     NS_ENSURE_TRUE(sc, NS_ERROR_UNEXPECTED);
55     nsCOMPtr<nsIObjectOutputStream> debugStream;
56     sc->GetDebugObjectOutputStream(objectOutput, getter_AddRefs(debugStream));
57     debugStream.forget(wrapperStream);
58   } else {
59     objectOutput.forget(wrapperStream);
60   }
61 #else
62   objectOutput.forget(wrapperStream);
63 #endif
64 
65   storageStream.forget(stream);
66   return NS_OK;
67 }
68 
NewBufferFromStorageStream(nsIStorageStream * storageStream,UniquePtr<char[]> * buffer,uint32_t * len)69 nsresult NewBufferFromStorageStream(nsIStorageStream* storageStream,
70                                     UniquePtr<char[]>* buffer, uint32_t* len) {
71   nsresult rv;
72   nsCOMPtr<nsIInputStream> inputStream;
73   rv = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
74   NS_ENSURE_SUCCESS(rv, rv);
75 
76   uint64_t avail64;
77   rv = inputStream->Available(&avail64);
78   NS_ENSURE_SUCCESS(rv, rv);
79   NS_ENSURE_TRUE(avail64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
80 
81   uint32_t avail = (uint32_t)avail64;
82   auto temp = MakeUnique<char[]>(avail);
83   uint32_t read;
84   rv = inputStream->Read(temp.get(), avail, &read);
85   if (NS_SUCCEEDED(rv) && avail != read) rv = NS_ERROR_UNEXPECTED;
86 
87   if (NS_FAILED(rv)) {
88     return rv;
89   }
90 
91   *len = avail;
92   *buffer = std::move(temp);
93   return NS_OK;
94 }
95 
96 static const char baseName[2][5] = {"gre/", "app/"};
97 
canonicalizeBase(nsAutoCString & spec,nsACString & out)98 static inline bool canonicalizeBase(nsAutoCString& spec, nsACString& out) {
99   nsAutoCString greBase, appBase;
100   nsresult rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::GRE, greBase);
101   if (NS_FAILED(rv) || !greBase.Length()) return false;
102 
103   rv = mozilla::Omnijar::GetURIString(mozilla::Omnijar::APP, appBase);
104   if (NS_FAILED(rv)) return false;
105 
106   bool underGre = !greBase.Compare(spec.get(), false, greBase.Length());
107   bool underApp =
108       appBase.Length() && !appBase.Compare(spec.get(), false, appBase.Length());
109 
110   if (!underGre && !underApp) return false;
111 
112   /**
113    * At this point, if both underGre and underApp are true, it can be one
114    * of the two following cases:
115    * - the GRE directory points to a subdirectory of the APP directory,
116    *   meaning spec points under GRE.
117    * - the APP directory points to a subdirectory of the GRE directory,
118    *   meaning spec points under APP.
119    * Checking the GRE and APP path length is enough to know in which case
120    * we are.
121    */
122   if (underGre && underApp && greBase.Length() < appBase.Length())
123     underGre = false;
124 
125   out.AppendLiteral("/resource/");
126   out.Append(
127       baseName[underGre ? mozilla::Omnijar::GRE : mozilla::Omnijar::APP]);
128   out.Append(Substring(spec, underGre ? greBase.Length() : appBase.Length()));
129   return true;
130 }
131 
132 /**
133  * ResolveURI transforms a chrome: or resource: URI into the URI for its
134  * underlying resource, or returns any other URI unchanged.
135  */
ResolveURI(nsIURI * in,nsIURI ** out)136 nsresult ResolveURI(nsIURI* in, nsIURI** out) {
137   nsresult rv;
138 
139   // Resolve resource:// URIs. At the end of this if/else block, we
140   // have both spec and uri variables identifying the same URI.
141   if (in->SchemeIs("resource")) {
142     nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
143     NS_ENSURE_SUCCESS(rv, rv);
144 
145     nsCOMPtr<nsIProtocolHandler> ph;
146     rv = ioService->GetProtocolHandler("resource", getter_AddRefs(ph));
147     NS_ENSURE_SUCCESS(rv, rv);
148 
149     nsCOMPtr<nsIResProtocolHandler> irph(do_QueryInterface(ph, &rv));
150     NS_ENSURE_SUCCESS(rv, rv);
151 
152     nsAutoCString spec;
153     rv = irph->ResolveURI(in, spec);
154     NS_ENSURE_SUCCESS(rv, rv);
155 
156     return ioService->NewURI(spec, nullptr, nullptr, out);
157   } else if (in->SchemeIs("chrome")) {
158     nsCOMPtr<nsIChromeRegistry> chromeReg =
159         mozilla::services::GetChromeRegistryService();
160     if (!chromeReg) return NS_ERROR_UNEXPECTED;
161 
162     return chromeReg->ConvertChromeURL(in, out);
163   }
164 
165   *out = do_AddRef(in).take();
166   return NS_OK;
167 }
168 
169 /**
170  * PathifyURI transforms uris into useful zip paths
171  * to make it easier to manipulate startup cache entries
172  * using standard zip tools.
173  * Transformations applied:
174  *  * resource:// URIs are resolved to their corresponding file/jar URI to
175  *    canonicalize resources URIs other than gre and app.
176  *  * Paths under GRE or APP directory have their base path replaced with
177  *    resource/gre or resource/app to avoid depending on install location.
178  *  * jar:file:///path/to/file.jar!/sub/path urls are replaced with
179  *    /path/to/file.jar/sub/path
180  *
181  *  The result is appended to the string passed in. Adding a prefix before
182  *  calling is recommended to avoid colliding with other cache users.
183  *
184  * For example, in the js loader (string is prefixed with jsloader by caller):
185  *  resource://gre/modules/XPCOMUtils.jsm or
186  *  file://$GRE_DIR/modules/XPCOMUtils.jsm or
187  *  jar:file://$GRE_DIR/omni.jar!/modules/XPCOMUtils.jsm becomes
188  *     jsloader/resource/gre/modules/XPCOMUtils.jsm
189  *  file://$PROFILE_DIR/extensions/{uuid}/components/component.js becomes
190  *     jsloader/$PROFILE_DIR/extensions/%7Buuid%7D/components/component.js
191  *  jar:file://$PROFILE_DIR/extensions/some.xpi!/components/component.js becomes
192  *     jsloader/$PROFILE_DIR/extensions/some.xpi/components/component.js
193  */
PathifyURI(nsIURI * in,nsACString & out)194 nsresult PathifyURI(nsIURI* in, nsACString& out) {
195   nsCOMPtr<nsIURI> uri;
196   nsresult rv = ResolveURI(in, getter_AddRefs(uri));
197   NS_ENSURE_SUCCESS(rv, rv);
198 
199   nsAutoCString spec;
200   rv = uri->GetSpec(spec);
201   NS_ENSURE_SUCCESS(rv, rv);
202 
203   if (!canonicalizeBase(spec, out)) {
204     if (uri->SchemeIs("file")) {
205       nsCOMPtr<nsIFileURL> baseFileURL;
206       baseFileURL = do_QueryInterface(uri, &rv);
207       NS_ENSURE_SUCCESS(rv, rv);
208 
209       nsAutoCString path;
210       rv = baseFileURL->GetPathQueryRef(path);
211       NS_ENSURE_SUCCESS(rv, rv);
212 
213       out.Append(path);
214     } else if (uri->SchemeIs("jar")) {
215       nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv);
216       NS_ENSURE_SUCCESS(rv, rv);
217 
218       nsCOMPtr<nsIURI> jarFileURI;
219       rv = jarURI->GetJARFile(getter_AddRefs(jarFileURI));
220       NS_ENSURE_SUCCESS(rv, rv);
221 
222       rv = PathifyURI(jarFileURI, out);
223       NS_ENSURE_SUCCESS(rv, rv);
224 
225       nsAutoCString path;
226       rv = jarURI->GetJAREntry(path);
227       NS_ENSURE_SUCCESS(rv, rv);
228       out.Append('/');
229       out.Append(path);
230     } else {  // Very unlikely
231       rv = uri->GetSpec(spec);
232       NS_ENSURE_SUCCESS(rv, rv);
233 
234       out.Append('/');
235       out.Append(spec);
236     }
237   }
238   return NS_OK;
239 }
240 
241 }  // namespace scache
242 }  // namespace mozilla
243