1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/content_setting_bubble_model.h"
6
7 #include "base/command_line.h"
8 #include "base/utf_string_conversions.h"
9 #include "chrome/browser/blocked_content_container.h"
10 #include "chrome/browser/content_settings/host_content_settings_map.h"
11 #include "chrome/browser/geolocation/geolocation_content_settings_map.h"
12 #include "chrome/browser/metrics/user_metrics.h"
13 #include "chrome/browser/prefs/pref_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/tab_contents/tab_specific_content_settings.h"
16 #include "chrome/browser/ui/collected_cookies_infobar_delegate.h"
17 #include "chrome/common/pref_names.h"
18 #include "content/browser/renderer_host/render_view_host.h"
19 #include "content/browser/tab_contents/tab_contents.h"
20 #include "content/browser/tab_contents/tab_contents_delegate.h"
21 #include "content/common/notification_service.h"
22 #include "grit/generated_resources.h"
23 #include "net/base/net_util.h"
24 #include "ui/base/l10n/l10n_util.h"
25
26 class ContentSettingTitleAndLinkModel : public ContentSettingBubbleModel {
27 public:
ContentSettingTitleAndLinkModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)28 ContentSettingTitleAndLinkModel(TabContents* tab_contents,
29 Profile* profile,
30 ContentSettingsType content_type)
31 : ContentSettingBubbleModel(tab_contents, profile, content_type) {
32 // Notifications do not have a bubble.
33 DCHECK_NE(content_type, CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
34 SetBlockedResources();
35 SetTitle();
36 SetManageLink();
37 }
38
~ContentSettingTitleAndLinkModel()39 virtual ~ContentSettingTitleAndLinkModel() {}
40
41 private:
SetBlockedResources()42 void SetBlockedResources() {
43 TabSpecificContentSettings* settings =
44 tab_contents()->GetTabSpecificContentSettings();
45 const std::set<std::string>& resources = settings->BlockedResourcesForType(
46 content_type());
47 for (std::set<std::string>::const_iterator it = resources.begin();
48 it != resources.end(); ++it) {
49 AddBlockedResource(*it);
50 }
51 }
52
SetTitle()53 void SetTitle() {
54 static const int kBlockedTitleIDs[] = {
55 IDS_BLOCKED_COOKIES_TITLE,
56 IDS_BLOCKED_IMAGES_TITLE,
57 IDS_BLOCKED_JAVASCRIPT_TITLE,
58 IDS_BLOCKED_PLUGINS_MESSAGE,
59 IDS_BLOCKED_POPUPS_TITLE,
60 0, // Geolocation does not have an overall title.
61 0, // Notifications do not have a bubble.
62 0, // Prerender does not have a bubble.
63 };
64 // Fields as for kBlockedTitleIDs, above.
65 static const int kResourceSpecificBlockedTitleIDs[] = {
66 0,
67 0,
68 0,
69 IDS_BLOCKED_PLUGINS_TITLE,
70 0,
71 0,
72 0,
73 0,
74 };
75 static const int kAccessedTitleIDs[] = {
76 IDS_ACCESSED_COOKIES_TITLE,
77 0,
78 0,
79 0,
80 0,
81 0,
82 0,
83 0,
84 };
85 COMPILE_ASSERT(arraysize(kAccessedTitleIDs) == CONTENT_SETTINGS_NUM_TYPES,
86 Need_a_setting_for_every_content_settings_type);
87 COMPILE_ASSERT(arraysize(kBlockedTitleIDs) == CONTENT_SETTINGS_NUM_TYPES,
88 Need_a_setting_for_every_content_settings_type);
89 COMPILE_ASSERT(arraysize(kResourceSpecificBlockedTitleIDs) ==
90 CONTENT_SETTINGS_NUM_TYPES,
91 Need_a_setting_for_every_content_settings_type);
92 const int *title_ids = kBlockedTitleIDs;
93 if (tab_contents() &&
94 tab_contents()->GetTabSpecificContentSettings()->IsContentAccessed(
95 content_type()) &&
96 !tab_contents()->GetTabSpecificContentSettings()->IsContentBlocked(
97 content_type())) {
98 title_ids = kAccessedTitleIDs;
99 } else if (!bubble_content().resource_identifiers.empty()) {
100 title_ids = kResourceSpecificBlockedTitleIDs;
101 }
102 if (title_ids[content_type()])
103 set_title(l10n_util::GetStringUTF8(title_ids[content_type()]));
104 }
105
SetManageLink()106 void SetManageLink() {
107 static const int kLinkIDs[] = {
108 IDS_BLOCKED_COOKIES_LINK,
109 IDS_BLOCKED_IMAGES_LINK,
110 IDS_BLOCKED_JAVASCRIPT_LINK,
111 IDS_BLOCKED_PLUGINS_LINK,
112 IDS_BLOCKED_POPUPS_LINK,
113 IDS_GEOLOCATION_BUBBLE_MANAGE_LINK,
114 0, // Notifications do not have a bubble.
115 0, // Prerender does not have a bubble.
116 };
117 COMPILE_ASSERT(arraysize(kLinkIDs) == CONTENT_SETTINGS_NUM_TYPES,
118 Need_a_setting_for_every_content_settings_type);
119 set_manage_link(l10n_util::GetStringUTF8(kLinkIDs[content_type()]));
120 }
121
OnManageLinkClicked()122 virtual void OnManageLinkClicked() {
123 if (tab_contents())
124 tab_contents()->delegate()->ShowContentSettingsPage(content_type());
125 }
126 };
127
128 class ContentSettingTitleLinkAndCustomModel
129 : public ContentSettingTitleAndLinkModel {
130 public:
ContentSettingTitleLinkAndCustomModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)131 ContentSettingTitleLinkAndCustomModel(TabContents* tab_contents,
132 Profile* profile,
133 ContentSettingsType content_type)
134 : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
135 SetCustomLink();
136 }
137
~ContentSettingTitleLinkAndCustomModel()138 virtual ~ContentSettingTitleLinkAndCustomModel() {}
139
140 private:
SetCustomLink()141 void SetCustomLink() {
142 static const int kCustomIDs[] = {
143 IDS_BLOCKED_COOKIES_INFO,
144 0, // Images do not have a custom link.
145 0, // Javascript doesn't have a custom link.
146 IDS_BLOCKED_PLUGINS_LOAD_ALL,
147 0, // Popups do not have a custom link.
148 0, // Geolocation custom links are set within that class.
149 0, // Notifications do not have a bubble.
150 0, // Prerender does not have a bubble.
151 };
152 COMPILE_ASSERT(arraysize(kCustomIDs) == CONTENT_SETTINGS_NUM_TYPES,
153 Need_a_setting_for_every_content_settings_type);
154 if (kCustomIDs[content_type()])
155 set_custom_link(l10n_util::GetStringUTF8(kCustomIDs[content_type()]));
156 }
157
OnCustomLinkClicked()158 virtual void OnCustomLinkClicked() {}
159 };
160
161
162 class ContentSettingSingleRadioGroup
163 : public ContentSettingTitleLinkAndCustomModel {
164 public:
ContentSettingSingleRadioGroup(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)165 ContentSettingSingleRadioGroup(TabContents* tab_contents,
166 Profile* profile,
167 ContentSettingsType content_type)
168 : ContentSettingTitleLinkAndCustomModel(tab_contents, profile,
169 content_type),
170 block_setting_(CONTENT_SETTING_BLOCK),
171 selected_item_(0) {
172 SetRadioGroup();
173 }
174
~ContentSettingSingleRadioGroup()175 virtual ~ContentSettingSingleRadioGroup() {
176 if (settings_changed()) {
177 ContentSetting setting =
178 selected_item_ == 0 ? CONTENT_SETTING_ALLOW : block_setting_;
179 const std::set<std::string>& resources =
180 bubble_content().resource_identifiers;
181 if (resources.empty()) {
182 AddException(setting, std::string());
183 } else {
184 for (std::set<std::string>::const_iterator it = resources.begin();
185 it != resources.end(); ++it) {
186 AddException(setting, *it);
187 }
188 }
189 }
190 }
191
192 protected:
settings_changed() const193 bool settings_changed() const {
194 return selected_item_ != bubble_content().radio_group.default_item;
195 }
196
197 private:
198 ContentSetting block_setting_;
199 int selected_item_;
200
201 // Initialize the radio group by setting the appropriate labels for the
202 // content type and setting the default value based on the content setting.
SetRadioGroup()203 void SetRadioGroup() {
204 GURL url = tab_contents()->GetURL();
205 std::wstring display_host_wide;
206 net::AppendFormattedHost(url,
207 UTF8ToWide(profile()->GetPrefs()->GetString(prefs::kAcceptLanguages)),
208 &display_host_wide, NULL, NULL);
209 std::string display_host(WideToUTF8(display_host_wide));
210
211 if (display_host.empty())
212 display_host = url.spec();
213
214 const std::set<std::string>& resources =
215 bubble_content().resource_identifiers;
216
217 RadioGroup radio_group;
218 radio_group.url = url;
219
220 static const int kAllowIDs[] = {
221 IDS_BLOCKED_COOKIES_UNBLOCK,
222 IDS_BLOCKED_IMAGES_UNBLOCK,
223 IDS_BLOCKED_JAVASCRIPT_UNBLOCK,
224 IDS_BLOCKED_PLUGINS_UNBLOCK_ALL,
225 IDS_BLOCKED_POPUPS_UNBLOCK,
226 0, // We don't manage geolocation here.
227 0, // Notifications do not have a bubble.
228 0, // Prerender does not have a bubble.
229 };
230 COMPILE_ASSERT(arraysize(kAllowIDs) == CONTENT_SETTINGS_NUM_TYPES,
231 Need_a_setting_for_every_content_settings_type);
232 // Fields as for kAllowIDs, above.
233 static const int kResourceSpecificAllowIDs[] = {
234 0,
235 0,
236 0,
237 IDS_BLOCKED_PLUGINS_UNBLOCK,
238 0,
239 0,
240 0,
241 0, // Prerender does not have a bubble.
242 };
243 COMPILE_ASSERT(
244 arraysize(kResourceSpecificAllowIDs) == CONTENT_SETTINGS_NUM_TYPES,
245 Need_a_setting_for_every_content_settings_type);
246 std::string radio_allow_label;
247 const int* allowIDs = resources.empty() ?
248 kAllowIDs : kResourceSpecificAllowIDs;
249 radio_allow_label = l10n_util::GetStringFUTF8(
250 allowIDs[content_type()], UTF8ToUTF16(display_host));
251
252 static const int kBlockIDs[] = {
253 IDS_BLOCKED_COOKIES_NO_ACTION,
254 IDS_BLOCKED_IMAGES_NO_ACTION,
255 IDS_BLOCKED_JAVASCRIPT_NO_ACTION,
256 IDS_BLOCKED_PLUGINS_NO_ACTION,
257 IDS_BLOCKED_POPUPS_NO_ACTION,
258 0, // We don't manage geolocation here.
259 0, // Notifications do not have a bubble.
260 0, // Prerender does not have a bubble.
261 };
262 COMPILE_ASSERT(arraysize(kBlockIDs) == CONTENT_SETTINGS_NUM_TYPES,
263 Need_a_setting_for_every_content_settings_type);
264 std::string radio_block_label;
265 radio_block_label = l10n_util::GetStringUTF8(kBlockIDs[content_type()]);
266
267 radio_group.radio_items.push_back(radio_allow_label);
268 radio_group.radio_items.push_back(radio_block_label);
269 HostContentSettingsMap* map = profile()->GetHostContentSettingsMap();
270 ContentSetting mostRestrictiveSetting;
271 if (resources.empty()) {
272 mostRestrictiveSetting =
273 map->GetContentSetting(url, content_type(), std::string());
274 } else {
275 mostRestrictiveSetting = CONTENT_SETTING_ALLOW;
276 for (std::set<std::string>::const_iterator it = resources.begin();
277 it != resources.end(); ++it) {
278 ContentSetting setting = map->GetContentSetting(url,
279 content_type(),
280 *it);
281 if (setting == CONTENT_SETTING_BLOCK) {
282 mostRestrictiveSetting = CONTENT_SETTING_BLOCK;
283 break;
284 }
285 if (setting == CONTENT_SETTING_ASK)
286 mostRestrictiveSetting = CONTENT_SETTING_ASK;
287 }
288 }
289 if (mostRestrictiveSetting == CONTENT_SETTING_ALLOW) {
290 radio_group.default_item = 0;
291 // |block_setting_| is already set to |CONTENT_SETTING_BLOCK|.
292 } else {
293 radio_group.default_item = 1;
294 block_setting_ = mostRestrictiveSetting;
295 }
296 selected_item_ = radio_group.default_item;
297 set_radio_group(radio_group);
298 }
299
AddException(ContentSetting setting,const std::string & resource_identifier)300 void AddException(ContentSetting setting,
301 const std::string& resource_identifier) {
302 profile()->GetHostContentSettingsMap()->AddExceptionForURL(
303 bubble_content().radio_group.url, content_type(), resource_identifier,
304 setting);
305 }
306
OnRadioClicked(int radio_index)307 virtual void OnRadioClicked(int radio_index) {
308 selected_item_ = radio_index;
309 }
310 };
311
312 class ContentSettingCookiesBubbleModel : public ContentSettingSingleRadioGroup {
313 public:
ContentSettingCookiesBubbleModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)314 ContentSettingCookiesBubbleModel(TabContents* tab_contents,
315 Profile* profile,
316 ContentSettingsType content_type)
317 : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
318 DCHECK_EQ(CONTENT_SETTINGS_TYPE_COOKIES, content_type);
319 set_custom_link_enabled(true);
320 }
321
~ContentSettingCookiesBubbleModel()322 virtual ~ContentSettingCookiesBubbleModel() {
323 if (settings_changed()) {
324 tab_contents()->AddInfoBar(
325 new CollectedCookiesInfoBarDelegate(tab_contents()));
326 }
327 }
328
329 private:
OnCustomLinkClicked()330 virtual void OnCustomLinkClicked() OVERRIDE {
331 if (tab_contents()) {
332 NotificationService::current()->Notify(
333 NotificationType::COLLECTED_COOKIES_SHOWN,
334 Source<TabSpecificContentSettings>(
335 tab_contents()->GetTabSpecificContentSettings()),
336 NotificationService::NoDetails());
337 tab_contents()->delegate()->ShowCollectedCookiesDialog(tab_contents());
338 }
339 }
340 };
341
342 class ContentSettingPluginBubbleModel : public ContentSettingSingleRadioGroup {
343 public:
ContentSettingPluginBubbleModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)344 ContentSettingPluginBubbleModel(TabContents* tab_contents,
345 Profile* profile,
346 ContentSettingsType content_type)
347 : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
348 DCHECK_EQ(content_type, CONTENT_SETTINGS_TYPE_PLUGINS);
349 set_custom_link_enabled(tab_contents && tab_contents->
350 GetTabSpecificContentSettings()->load_plugins_link_enabled());
351 }
352
~ContentSettingPluginBubbleModel()353 virtual ~ContentSettingPluginBubbleModel() {}
354
355 private:
OnCustomLinkClicked()356 virtual void OnCustomLinkClicked() OVERRIDE {
357 UserMetrics::RecordAction(UserMetricsAction("ClickToPlay_LoadAll_Bubble"));
358 DCHECK(tab_contents());
359 tab_contents()->render_view_host()->LoadBlockedPlugins();
360 set_custom_link_enabled(false);
361 tab_contents()->GetTabSpecificContentSettings()->
362 set_load_plugins_link_enabled(false);
363 }
364 };
365
366 class ContentSettingPopupBubbleModel : public ContentSettingSingleRadioGroup {
367 public:
ContentSettingPopupBubbleModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)368 ContentSettingPopupBubbleModel(TabContents* tab_contents,
369 Profile* profile,
370 ContentSettingsType content_type)
371 : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
372 SetPopups();
373 }
374
~ContentSettingPopupBubbleModel()375 virtual ~ContentSettingPopupBubbleModel() {}
376
377 private:
SetPopups()378 void SetPopups() {
379 // check for crbug.com/53176
380 if (!tab_contents()->blocked_content_container())
381 return;
382 std::vector<TabContents*> blocked_contents;
383 tab_contents()->blocked_content_container()->GetBlockedContents(
384 &blocked_contents);
385 for (std::vector<TabContents*>::const_iterator
386 i(blocked_contents.begin()); i != blocked_contents.end(); ++i) {
387 std::string title(UTF16ToUTF8((*i)->GetTitle()));
388 // The popup may not have committed a load yet, in which case it won't
389 // have a URL or title.
390 if (title.empty())
391 title = l10n_util::GetStringUTF8(IDS_TAB_LOADING_TITLE);
392 PopupItem popup_item;
393 popup_item.title = title;
394 popup_item.bitmap = (*i)->GetFavicon();
395 popup_item.tab_contents = (*i);
396 add_popup(popup_item);
397 }
398 }
399
OnPopupClicked(int index)400 virtual void OnPopupClicked(int index) {
401 if (tab_contents() && tab_contents()->blocked_content_container()) {
402 tab_contents()->blocked_content_container()->LaunchForContents(
403 bubble_content().popup_items[index].tab_contents);
404 }
405 }
406 };
407
408 class ContentSettingDomainListBubbleModel
409 : public ContentSettingTitleAndLinkModel {
410 public:
ContentSettingDomainListBubbleModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)411 ContentSettingDomainListBubbleModel(TabContents* tab_contents,
412 Profile* profile,
413 ContentSettingsType content_type)
414 : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
415 DCHECK_EQ(CONTENT_SETTINGS_TYPE_GEOLOCATION, content_type) <<
416 "SetDomains currently only supports geolocation content type";
417 SetDomainsAndCustomLink();
418 }
419
~ContentSettingDomainListBubbleModel()420 virtual ~ContentSettingDomainListBubbleModel() {}
421
422 private:
MaybeAddDomainList(const std::set<std::string> & hosts,int title_id)423 void MaybeAddDomainList(const std::set<std::string>& hosts, int title_id) {
424 if (!hosts.empty()) {
425 DomainList domain_list;
426 domain_list.title = l10n_util::GetStringUTF8(title_id);
427 domain_list.hosts = hosts;
428 add_domain_list(domain_list);
429 }
430 }
SetDomainsAndCustomLink()431 void SetDomainsAndCustomLink() {
432 TabSpecificContentSettings* content_settings =
433 tab_contents()->GetTabSpecificContentSettings();
434 const GeolocationSettingsState& settings =
435 content_settings->geolocation_settings_state();
436 GeolocationSettingsState::FormattedHostsPerState formatted_hosts_per_state;
437 unsigned int tab_state_flags = 0;
438 settings.GetDetailedInfo(&formatted_hosts_per_state, &tab_state_flags);
439 // Divide the tab's current geolocation users into sets according to their
440 // permission state.
441 MaybeAddDomainList(formatted_hosts_per_state[CONTENT_SETTING_ALLOW],
442 IDS_GEOLOCATION_BUBBLE_SECTION_ALLOWED);
443
444 MaybeAddDomainList(formatted_hosts_per_state[CONTENT_SETTING_BLOCK],
445 IDS_GEOLOCATION_BUBBLE_SECTION_DENIED);
446
447 if (tab_state_flags & GeolocationSettingsState::TABSTATE_HAS_EXCEPTION) {
448 set_custom_link(l10n_util::GetStringUTF8(
449 IDS_GEOLOCATION_BUBBLE_CLEAR_LINK));
450 set_custom_link_enabled(true);
451 } else if (tab_state_flags &
452 GeolocationSettingsState::TABSTATE_HAS_CHANGED) {
453 set_custom_link(l10n_util::GetStringUTF8(
454 IDS_GEOLOCATION_BUBBLE_REQUIRE_RELOAD_TO_CLEAR));
455 }
456 }
OnCustomLinkClicked()457 virtual void OnCustomLinkClicked() OVERRIDE {
458 if (!tab_contents())
459 return;
460 // Reset this embedder's entry to default for each of the requesting
461 // origins currently on the page.
462 const GURL& embedder_url = tab_contents()->GetURL();
463 TabSpecificContentSettings* content_settings =
464 tab_contents()->GetTabSpecificContentSettings();
465 const GeolocationSettingsState::StateMap& state_map =
466 content_settings->geolocation_settings_state().state_map();
467 GeolocationContentSettingsMap* settings_map =
468 profile()->GetGeolocationContentSettingsMap();
469 for (GeolocationSettingsState::StateMap::const_iterator it =
470 state_map.begin(); it != state_map.end(); ++it) {
471 settings_map->SetContentSetting(it->first, embedder_url,
472 CONTENT_SETTING_DEFAULT);
473 }
474 }
475 };
476
477 // static
478 ContentSettingBubbleModel*
CreateContentSettingBubbleModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)479 ContentSettingBubbleModel::CreateContentSettingBubbleModel(
480 TabContents* tab_contents,
481 Profile* profile,
482 ContentSettingsType content_type) {
483 if (content_type == CONTENT_SETTINGS_TYPE_COOKIES) {
484 return new ContentSettingCookiesBubbleModel(tab_contents, profile,
485 content_type);
486 }
487 if (content_type == CONTENT_SETTINGS_TYPE_POPUPS) {
488 return new ContentSettingPopupBubbleModel(tab_contents, profile,
489 content_type);
490 }
491 if (content_type == CONTENT_SETTINGS_TYPE_GEOLOCATION) {
492 return new ContentSettingDomainListBubbleModel(tab_contents, profile,
493 content_type);
494 }
495 if (content_type == CONTENT_SETTINGS_TYPE_PLUGINS) {
496 return new ContentSettingPluginBubbleModel(tab_contents, profile,
497 content_type);
498 }
499 return new ContentSettingSingleRadioGroup(tab_contents, profile,
500 content_type);
501 }
502
ContentSettingBubbleModel(TabContents * tab_contents,Profile * profile,ContentSettingsType content_type)503 ContentSettingBubbleModel::ContentSettingBubbleModel(
504 TabContents* tab_contents,
505 Profile* profile,
506 ContentSettingsType content_type)
507 : tab_contents_(tab_contents),
508 profile_(profile),
509 content_type_(content_type) {
510 registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
511 Source<TabContents>(tab_contents));
512 }
513
~ContentSettingBubbleModel()514 ContentSettingBubbleModel::~ContentSettingBubbleModel() {
515 }
516
RadioGroup()517 ContentSettingBubbleModel::RadioGroup::RadioGroup() : default_item(0) {}
518
~RadioGroup()519 ContentSettingBubbleModel::RadioGroup::~RadioGroup() {}
520
DomainList()521 ContentSettingBubbleModel::DomainList::DomainList() {}
522
~DomainList()523 ContentSettingBubbleModel::DomainList::~DomainList() {}
524
BubbleContent()525 ContentSettingBubbleModel::BubbleContent::BubbleContent()
526 : custom_link_enabled(false) {
527 }
528
~BubbleContent()529 ContentSettingBubbleModel::BubbleContent::~BubbleContent() {}
530
531
AddBlockedResource(const std::string & resource_identifier)532 void ContentSettingBubbleModel::AddBlockedResource(
533 const std::string& resource_identifier) {
534 bubble_content_.resource_identifiers.insert(resource_identifier);
535 }
536
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)537 void ContentSettingBubbleModel::Observe(NotificationType type,
538 const NotificationSource& source,
539 const NotificationDetails& details) {
540 DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
541 DCHECK(source == Source<TabContents>(tab_contents_));
542 tab_contents_ = NULL;
543 }
544