OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/url_request/sdch_dictionary_fetcher.h" | 5 #include "net/url_request/sdch_dictionary_fetcher.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 #include <utility> | 8 #include <utility> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
11 #include "base/bind.h" | 11 #include "base/bind.h" |
12 #include "base/callback.h" | 12 #include "base/callback.h" |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
14 #include "base/macros.h" | 14 #include "base/macros.h" |
15 #include "base/run_loop.h" | 15 #include "base/run_loop.h" |
16 #include "base/strings/stringprintf.h" | 16 #include "base/strings/stringprintf.h" |
17 #include "base/thread_task_runner_handle.h" | 17 #include "base/thread_task_runner_handle.h" |
18 #include "net/base/load_flags.h" | 18 #include "net/base/load_flags.h" |
19 #include "net/base/sdch_manager.h" | 19 #include "net/base/sdch_manager.h" |
20 #include "net/http/http_response_headers.h" | 20 #include "net/http/http_response_headers.h" |
21 #include "net/url_request/url_request_data_job.h" | 21 #include "net/url_request/url_request_data_job.h" |
22 #include "net/url_request/url_request_filter.h" | 22 #include "net/url_request/url_request_filter.h" |
23 #include "net/url_request/url_request_interceptor.h" | 23 #include "net/url_request/url_request_interceptor.h" |
| 24 #include "net/url_request/url_request_redirect_job.h" |
24 #include "net/url_request/url_request_test_util.h" | 25 #include "net/url_request/url_request_test_util.h" |
25 #include "testing/gtest/include/gtest/gtest.h" | 26 #include "testing/gtest/include/gtest/gtest.h" |
26 | 27 |
27 namespace net { | 28 namespace net { |
28 | 29 |
29 namespace { | 30 namespace { |
30 | 31 |
31 const char kSampleBufferContext[] = "This is a sample buffer."; | 32 const char kSampleBufferContext[] = "This is a sample buffer."; |
32 const char kTestDomain[] = "top.domain.test"; | 33 const char kTestDomain1[] = "top.domain.test"; |
| 34 const char kTestDomain2[] = "top2.domain.test"; |
33 | 35 |
34 class URLRequestSpecifiedResponseJob : public URLRequestSimpleJob { | 36 class URLRequestSpecifiedResponseJob : public URLRequestSimpleJob { |
35 public: | 37 public: |
36 // Called on destruction with load flags used for this request. | 38 // Called on destruction with load flags used for this request. |
37 typedef base::Callback<void(int)> DestructionCallback; | 39 typedef base::Callback<void(int)> DestructionCallback; |
38 | 40 |
39 URLRequestSpecifiedResponseJob( | 41 URLRequestSpecifiedResponseJob( |
40 URLRequest* request, | 42 URLRequest* request, |
41 NetworkDelegate* network_delegate, | 43 NetworkDelegate* network_delegate, |
42 const HttpResponseInfo& response_info_to_return, | 44 const HttpResponseInfo& response_info_to_return, |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
75 return OK; | 77 return OK; |
76 } | 78 } |
77 | 79 |
78 const HttpResponseInfo response_info_to_return_; | 80 const HttpResponseInfo response_info_to_return_; |
79 int last_load_flags_seen_; | 81 int last_load_flags_seen_; |
80 const DestructionCallback destruction_callback_; | 82 const DestructionCallback destruction_callback_; |
81 | 83 |
82 DISALLOW_COPY_AND_ASSIGN(URLRequestSpecifiedResponseJob); | 84 DISALLOW_COPY_AND_ASSIGN(URLRequestSpecifiedResponseJob); |
83 }; | 85 }; |
84 | 86 |
85 class SpecifiedResponseJobInterceptor : public URLRequestInterceptor { | 87 // Wrap URLRequestRedirectJob in a destruction callback. |
| 88 class TestURLRequestRedirectJob : public URLRequestRedirectJob { |
86 public: | 89 public: |
87 // A callback to be called whenever a URLRequestSpecifiedResponseJob is | 90 TestURLRequestRedirectJob(URLRequest* request, |
88 // created or destroyed. The first argument will be the change in number of | 91 NetworkDelegate* network_delegate, |
89 // jobs (i.e. +1 for created, -1 for destroyed). | 92 const GURL& redirect_destination, |
90 // The second argument will be undefined if the job is being created, | 93 ResponseCode response_code, |
91 // and will contain the load flags passed to the request the | 94 const std::string& redirect_reason, |
92 // job was created for if the job is being destroyed. | 95 base::Closure destruction_callback) |
| 96 : URLRequestRedirectJob(request, |
| 97 network_delegate, |
| 98 redirect_destination, |
| 99 response_code, |
| 100 redirect_reason), |
| 101 destruction_callback_(destruction_callback) {} |
| 102 ~TestURLRequestRedirectJob() override { destruction_callback_.Run(); } |
| 103 |
| 104 private: |
| 105 const base::Closure destruction_callback_; |
| 106 }; |
| 107 |
| 108 static const char* redirect_signal = "/redirect/"; |
| 109 class SDCHTestRequestInterceptor : public URLRequestInterceptor { |
| 110 public: |
| 111 // A callback to be called whenever a URLRequestJob child of this |
| 112 // interceptor is created or destroyed. The first argument will be the |
| 113 // change in number of jobs (i.e. +1 for created, -1 for destroyed). |
| 114 // The second argument will be undefined if the job is being created |
| 115 // or a redirect job is being destroyed, and (for non-redirect job |
| 116 // destruction) will contain the load flags passed to the request the |
| 117 // job was created for. |
93 typedef base::Callback<void(int outstanding_job_delta, | 118 typedef base::Callback<void(int outstanding_job_delta, |
94 int destruction_load_flags)> LifecycleCallback; | 119 int destruction_load_flags)> LifecycleCallback; |
95 | 120 |
96 // |*info| will be returned from all child URLRequestSpecifiedResponseJobs. | 121 // |*info| will be returned from all child URLRequestSpecifiedResponseJobs. |
97 // Note that: a) this pointer is shared with the caller, and the caller must | 122 // Note that: a) this pointer is shared with the caller, and the caller must |
98 // guarantee that |*info| outlives the SpecifiedResponseJobInterceptor, and | 123 // guarantee that |*info| outlives the SDCHTestRequestInterceptor, and |
99 // b) |*info| is mutable, and changes to should propagate to | 124 // b) |*info| is mutable, and changes to should propagate to |
100 // URLRequestSpecifiedResponseJobs created after any change. | 125 // URLRequestSpecifiedResponseJobs created after any change. |
101 SpecifiedResponseJobInterceptor(HttpResponseInfo* http_response_info, | 126 SDCHTestRequestInterceptor(HttpResponseInfo* http_response_info, |
102 const LifecycleCallback& lifecycle_callback) | 127 const LifecycleCallback& lifecycle_callback) |
103 : http_response_info_(http_response_info), | 128 : http_response_info_(http_response_info), |
104 lifecycle_callback_(lifecycle_callback) { | 129 lifecycle_callback_(lifecycle_callback) { |
105 DCHECK(!lifecycle_callback_.is_null()); | 130 DCHECK(!lifecycle_callback_.is_null()); |
106 } | 131 } |
107 ~SpecifiedResponseJobInterceptor() override {} | 132 ~SDCHTestRequestInterceptor() override {} |
108 | 133 |
109 URLRequestJob* MaybeInterceptRequest( | 134 URLRequestJob* MaybeInterceptRequest( |
110 URLRequest* request, | 135 URLRequest* request, |
111 NetworkDelegate* network_delegate) const override { | 136 NetworkDelegate* network_delegate) const override { |
112 lifecycle_callback_.Run(1, 0); | 137 lifecycle_callback_.Run(1, 0); |
113 | 138 |
| 139 std::string path = request->url().path(); |
| 140 if (path.substr(0, strlen(redirect_signal)) == redirect_signal) { |
| 141 return new TestURLRequestRedirectJob( |
| 142 request, network_delegate, GURL(path.substr(strlen(redirect_signal))), |
| 143 URLRequestRedirectJob::REDIRECT_307_TEMPORARY_REDIRECT, "testing", |
| 144 base::Bind(lifecycle_callback_, -1, 0)); |
| 145 } |
| 146 |
114 return new URLRequestSpecifiedResponseJob( | 147 return new URLRequestSpecifiedResponseJob( |
115 request, network_delegate, *http_response_info_, | 148 request, network_delegate, *http_response_info_, |
116 base::Bind(lifecycle_callback_, -1)); | 149 base::Bind(lifecycle_callback_, -1)); |
117 } | 150 } |
118 | 151 |
119 // The caller must ensure that both |*http_response_info| and the | 152 // The caller must ensure that both |*http_response_info| and the |
120 // callback remain valid for the lifetime of the | 153 // callback remain valid for the lifetime of the |
121 // SpecifiedResponseJobInterceptor (i.e. until Unregister() is called). | 154 // SDCHTestRequestInterceptor (i.e. until Unregister() is called). |
122 static void RegisterWithFilter(HttpResponseInfo* http_response_info, | 155 static void RegisterWithFilter(HttpResponseInfo* http_response_info, |
123 const LifecycleCallback& lifecycle_callback) { | 156 const LifecycleCallback& lifecycle_callback) { |
124 scoped_ptr<SpecifiedResponseJobInterceptor> interceptor( | 157 URLRequestFilter::GetInstance()->AddHostnameInterceptor( |
125 new SpecifiedResponseJobInterceptor(http_response_info, | 158 "http", kTestDomain1, |
126 lifecycle_callback)); | 159 scoped_ptr<URLRequestInterceptor>(new SDCHTestRequestInterceptor( |
| 160 http_response_info, lifecycle_callback))); |
127 | 161 |
128 URLRequestFilter::GetInstance()->AddHostnameInterceptor( | 162 URLRequestFilter::GetInstance()->AddHostnameInterceptor( |
129 "http", kTestDomain, std::move(interceptor)); | 163 "https", kTestDomain1, |
| 164 scoped_ptr<URLRequestInterceptor>(new SDCHTestRequestInterceptor( |
| 165 http_response_info, lifecycle_callback))); |
| 166 |
| 167 URLRequestFilter::GetInstance()->AddHostnameInterceptor( |
| 168 "http", kTestDomain2, |
| 169 scoped_ptr<URLRequestInterceptor>(new SDCHTestRequestInterceptor( |
| 170 http_response_info, lifecycle_callback))); |
| 171 |
| 172 URLRequestFilter::GetInstance()->AddHostnameInterceptor( |
| 173 "https", kTestDomain2, |
| 174 scoped_ptr<URLRequestInterceptor>(new SDCHTestRequestInterceptor( |
| 175 http_response_info, lifecycle_callback))); |
130 } | 176 } |
131 | 177 |
132 static void Unregister() { | 178 static void Unregister() { |
133 URLRequestFilter::GetInstance()->RemoveHostnameHandler("http", kTestDomain); | 179 URLRequestFilter::GetInstance()->RemoveHostnameHandler("http", |
| 180 kTestDomain1); |
| 181 URLRequestFilter::GetInstance()->RemoveHostnameHandler("https", |
| 182 kTestDomain1); |
| 183 URLRequestFilter::GetInstance()->RemoveHostnameHandler("http", |
| 184 kTestDomain2); |
| 185 URLRequestFilter::GetInstance()->RemoveHostnameHandler("https", |
| 186 kTestDomain2); |
134 } | 187 } |
135 | 188 |
136 private: | 189 private: |
137 HttpResponseInfo* http_response_info_; | 190 HttpResponseInfo* http_response_info_; |
138 LifecycleCallback lifecycle_callback_; | 191 LifecycleCallback lifecycle_callback_; |
139 DISALLOW_COPY_AND_ASSIGN(SpecifiedResponseJobInterceptor); | 192 DISALLOW_COPY_AND_ASSIGN(SDCHTestRequestInterceptor); |
140 }; | 193 }; |
141 | 194 |
142 // Local test infrastructure | 195 // Local test infrastructure |
143 // * URLRequestSpecifiedResponseJob: A URLRequestJob that returns | 196 // * URLRequestSpecifiedResponseJob: A URLRequestJob that returns |
144 // a different but derivable response for each URL (used for all | 197 // a different but derivable response for each URL (used for all |
145 // url requests in this file). This class is initialized with | 198 // url requests in this file). This class is initialized with |
146 // the HttpResponseInfo to return (if any), as well as a callback | 199 // the HttpResponseInfo to return (if any), as well as a callback |
147 // that is called when the class is destroyed. That callback | 200 // that is called when the class is destroyed. That callback |
148 // takes as arguemnt the load flags used for the request the | 201 // takes as arguemnt the load flags used for the request the |
149 // job was created for. | 202 // job was created for. |
150 // * SpecifiedResponseJobInterceptor: This class is a | 203 // * SDCHTestRequestInterceptor: This class is a |
151 // URLRequestInterceptor that generates the class above. It is constructed | 204 // URLRequestInterceptor that generates either the class above or an |
| 205 // instance of URLRequestRedirectJob (if the first component of the path |
| 206 // is "redirect"). It is constructed |
152 // with a pointer to the (mutable) resposne info that should be | 207 // with a pointer to the (mutable) resposne info that should be |
153 // returned from the URLRequestSpecifiedResponseJob children, as well as | 208 // returned from constructed URLRequestSpecifiedResponseJobs, as well as |
154 // a callback that is run when URLRequestSpecifiedResponseJobs are | 209 // a callback that is run when URLRequestSpecifiedResponseJobs are |
155 // created or destroyed. | 210 // created or destroyed. |
156 // * SdchDictionaryFetcherTest: This class registers the above interceptor, | 211 // * SdchDictionaryFetcherTest: This class registers the above interceptor, |
157 // tracks the number of jobs requested and the subset of those | 212 // tracks the number of jobs requested and the subset of those |
158 // that are still outstanding. It exports an interface to wait until there | 213 // that are still outstanding. It exports an interface to wait until there |
159 // are no jobs outstanding. It shares an HttpResponseInfo structure | 214 // are no jobs outstanding. It shares an HttpResponseInfo structure |
160 // with the SpecifiedResponseJobInterceptor to control the response | 215 // with the SDCHTestRequestInterceptor to control the response |
161 // information returned by the jbos. | 216 // information returned by the jbos. |
162 // The standard pattern for tests is to schedule a dictionary fetch, wait | 217 // The standard pattern for tests is to schedule a dictionary fetch, wait |
163 // for no jobs outstanding, then test that the fetch results are as expected. | 218 // for no jobs outstanding, then test that the fetch results are as expected. |
164 | 219 |
165 class SdchDictionaryFetcherTest : public ::testing::Test { | 220 class SdchDictionaryFetcherTest : public ::testing::Test { |
166 public: | 221 public: |
167 struct DictionaryAdditions { | 222 struct DictionaryAdditions { |
168 DictionaryAdditions(const std::string& dictionary_text, | 223 DictionaryAdditions(const std::string& dictionary_text, |
169 const GURL& dictionary_url) | 224 const GURL& dictionary_url) |
170 : dictionary_text(dictionary_text), dictionary_url(dictionary_url) {} | 225 : dictionary_text(dictionary_text), dictionary_url(dictionary_url) {} |
171 | 226 |
172 std::string dictionary_text; | 227 std::string dictionary_text; |
173 GURL dictionary_url; | 228 GURL dictionary_url; |
174 }; | 229 }; |
175 | 230 |
176 SdchDictionaryFetcherTest() | 231 SdchDictionaryFetcherTest() |
177 : jobs_requested_(0), | 232 : jobs_requested_(0), |
178 jobs_outstanding_(0), | 233 jobs_outstanding_(0), |
179 last_load_flags_seen_(LOAD_NORMAL), | 234 last_load_flags_seen_(LOAD_NORMAL), |
180 context_(new TestURLRequestContext), | 235 context_(new TestURLRequestContext), |
181 fetcher_(new SdchDictionaryFetcher(context_.get())), | 236 fetcher_(new SdchDictionaryFetcher(context_.get())), |
182 factory_(this) { | 237 factory_(this) { |
183 response_info_to_return_.request_time = base::Time::Now(); | 238 response_info_to_return_.request_time = base::Time::Now(); |
184 response_info_to_return_.response_time = base::Time::Now(); | 239 response_info_to_return_.response_time = base::Time::Now(); |
185 SpecifiedResponseJobInterceptor::RegisterWithFilter( | 240 SDCHTestRequestInterceptor::RegisterWithFilter( |
186 &response_info_to_return_, | 241 &response_info_to_return_, |
187 base::Bind(&SdchDictionaryFetcherTest::OnNumberJobsChanged, | 242 base::Bind(&SdchDictionaryFetcherTest::OnNumberJobsChanged, |
188 factory_.GetWeakPtr())); | 243 factory_.GetWeakPtr())); |
189 } | 244 } |
190 | 245 |
191 ~SdchDictionaryFetcherTest() override { | 246 ~SdchDictionaryFetcherTest() override { |
192 SpecifiedResponseJobInterceptor::Unregister(); | 247 SDCHTestRequestInterceptor::Unregister(); |
193 } | 248 } |
194 | 249 |
195 void OnDictionaryFetched(const std::string& dictionary_text, | 250 void OnDictionaryFetched(const std::string& dictionary_text, |
196 const GURL& dictionary_url, | 251 const GURL& dictionary_url, |
197 const BoundNetLog& net_log, | 252 const BoundNetLog& net_log, |
198 bool was_from_cache) { | 253 bool was_from_cache) { |
199 dictionary_additions_.push_back( | 254 dictionary_additions_.push_back( |
200 DictionaryAdditions(dictionary_text, dictionary_url)); | 255 DictionaryAdditions(dictionary_text, dictionary_url)); |
201 } | 256 } |
202 | 257 |
203 // Return (in |*out|) all dictionary additions since the last time | 258 // Return (in |*out|) all dictionary additions since the last time |
204 // this function was called. | 259 // this function was called. |
205 void GetDictionaryAdditions(std::vector<DictionaryAdditions>* out) { | 260 void GetDictionaryAdditions(std::vector<DictionaryAdditions>* out) { |
206 out->swap(dictionary_additions_); | 261 out->swap(dictionary_additions_); |
207 dictionary_additions_.clear(); | 262 dictionary_additions_.clear(); |
208 } | 263 } |
209 | 264 |
210 SdchDictionaryFetcher* fetcher() { return fetcher_.get(); } | 265 SdchDictionaryFetcher* fetcher() { return fetcher_.get(); } |
211 | 266 |
212 // May not be called outside the SetUp()/TearDown() interval. | 267 // May not be called outside the SetUp()/TearDown() interval. |
213 int jobs_requested() const { return jobs_requested_; } | 268 int jobs_requested() const { return jobs_requested_; } |
214 | 269 |
215 GURL PathToGurl(const char* path) const { | 270 GURL PathToGurl(const char* path) const { |
216 std::string gurl_string("http://"); | 271 std::string gurl_string("http://"); |
217 gurl_string += kTestDomain; | 272 gurl_string += kTestDomain1; |
218 gurl_string += "/"; | 273 gurl_string += "/"; |
219 gurl_string += path; | 274 gurl_string += path; |
220 return GURL(gurl_string); | 275 return GURL(gurl_string); |
221 } | 276 } |
222 | 277 |
223 // Block until there are no outstanding URLRequestSpecifiedResponseJobs. | 278 // Block until there are no outstanding URLRequestSpecifiedResponseJobs. |
224 void WaitForNoJobs() { | 279 void WaitForNoJobs() { |
225 // A job may be started after the previous one was destroyed, with a brief | 280 // A job may be started after the previous one was destroyed, with a brief |
226 // period of 0 jobs in between, so may have to start the run loop multiple | 281 // period of 0 jobs in between, so may have to start the run loop multiple |
227 // times. | 282 // times. |
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
427 EXPECT_EQ(1, jobs_requested()); | 482 EXPECT_EQ(1, jobs_requested()); |
428 | 483 |
429 fetcher()->Cancel(); | 484 fetcher()->Cancel(); |
430 WaitForNoJobs(); | 485 WaitForNoJobs(); |
431 EXPECT_TRUE(fetcher()->Schedule(dictionary_url, GetDefaultCallback())); | 486 EXPECT_TRUE(fetcher()->Schedule(dictionary_url, GetDefaultCallback())); |
432 | 487 |
433 WaitForNoJobs(); | 488 WaitForNoJobs(); |
434 EXPECT_EQ(2, jobs_requested()); | 489 EXPECT_EQ(2, jobs_requested()); |
435 } | 490 } |
436 | 491 |
| 492 TEST_F(SdchDictionaryFetcherTest, InOriginRedirect) { |
| 493 GURL dictionary_url(PathToGurl("dictionary")); |
| 494 GURL local_redirect_url(dictionary_url.GetWithEmptyPath().spec() + |
| 495 "redirect/" + dictionary_url.spec()); |
| 496 EXPECT_TRUE(fetcher()->Schedule(local_redirect_url, GetDefaultCallback())); |
| 497 WaitForNoJobs(); |
| 498 |
| 499 // Both the redirect job and the job retrieving the actual dictionary |
| 500 // should have been executed. |
| 501 EXPECT_EQ(2, jobs_requested()); |
| 502 std::vector<DictionaryAdditions> additions; |
| 503 GetDictionaryAdditions(&additions); |
| 504 EXPECT_EQ(1u, additions.size()); |
| 505 } |
| 506 |
| 507 TEST_F(SdchDictionaryFetcherTest, CrossSecurityRedirect) { |
| 508 std::string dictionary_url_spec("https://" + std::string(kTestDomain1) + |
| 509 "/dictionary"); |
| 510 GURL local_redirect_url("http://" + std::string(kTestDomain1) + "/redirect/" + |
| 511 dictionary_url_spec); |
| 512 |
| 513 EXPECT_TRUE(fetcher()->Schedule(local_redirect_url, GetDefaultCallback())); |
| 514 WaitForNoJobs(); |
| 515 |
| 516 // The fetcher should have refused the redirect job. |
| 517 EXPECT_EQ(1, jobs_requested()); |
| 518 std::vector<DictionaryAdditions> additions; |
| 519 GetDictionaryAdditions(&additions); |
| 520 // And there should be no dictionaries added. |
| 521 EXPECT_EQ(0u, additions.size()); |
| 522 } |
| 523 |
| 524 TEST_F(SdchDictionaryFetcherTest, CrossSiteRedirect) { |
| 525 std::string dictionary_url_spec("https://" + std::string(kTestDomain1) + |
| 526 "/dictionary"); |
| 527 GURL local_redirect_url("https://" + std::string(kTestDomain2) + |
| 528 "/redirect/" + dictionary_url_spec); |
| 529 |
| 530 EXPECT_TRUE(fetcher()->Schedule(local_redirect_url, GetDefaultCallback())); |
| 531 WaitForNoJobs(); |
| 532 |
| 533 // The fetcher should have refused the redirect job. |
| 534 EXPECT_EQ(1, jobs_requested()); |
| 535 std::vector<DictionaryAdditions> additions; |
| 536 GetDictionaryAdditions(&additions); |
| 537 // And there should be no dictionaries added. |
| 538 EXPECT_EQ(0u, additions.size()); |
| 539 } |
| 540 |
| 541 TEST_F(SdchDictionaryFetcherTest, CrossSiteSecurityRedirect) { |
| 542 std::string dictionary_url_spec("https://" + std::string(kTestDomain1) + |
| 543 "/dictionary"); |
| 544 GURL local_redirect_url("http://" + std::string(kTestDomain2) + "/redirect/" + |
| 545 dictionary_url_spec); |
| 546 |
| 547 EXPECT_TRUE(fetcher()->Schedule(local_redirect_url, GetDefaultCallback())); |
| 548 WaitForNoJobs(); |
| 549 |
| 550 // The fetcher should have refused the redirect job. |
| 551 EXPECT_EQ(1, jobs_requested()); |
| 552 std::vector<DictionaryAdditions> additions; |
| 553 GetDictionaryAdditions(&additions); |
| 554 // And there should be no dictionaries added. |
| 555 EXPECT_EQ(0u, additions.size()); |
| 556 } |
| 557 |
437 } // namespace | 558 } // namespace |
438 | 559 |
439 } // namespace net | 560 } // namespace net |
OLD | NEW |