1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_shell.hxx"
30 
31 #include "osl/process.h"
32 #include "rtl/ustring.hxx"
33 #include "rtl/string.hxx"
34 #include "rtl/strbuf.hxx"
35 
36 #include "osl/thread.h"
37 #include "recently_used_file.hxx"
38 
39 #include "internal/xml_parser.hxx"
40 #include "internal/i_xml_parser_event_handler.hxx"
41 
42 #include <map>
43 #include <vector>
44 #include <algorithm>
45 #include <functional>
46 #include <string.h>
47 
48 namespace /* private */ {
49     //########################################
50     typedef std::vector<string_t> string_container_t;
51 
52     #define TAG_RECENT_FILES "RecentFiles"
53     #define TAG_RECENT_ITEM  "RecentItem"
54     #define TAG_URI          "URI"
55     #define TAG_MIME_TYPE    "Mime-Type"
56     #define TAG_TIMESTAMP    "Timestamp"
57     #define TAG_PRIVATE      "Private"
58     #define TAG_GROUPS       "Groups"
59     #define TAG_GROUP        "Group"
60 
61     //------------------------------------------------
62     // compare two string_t's case insensitive, may also be done
63     // by specifying special traits for the string type but in this
64     // case it's easier to do it this way
65     struct str_icase_cmp :
66         public std::binary_function<string_t, string_t, bool>
67     {
68         bool operator() (const string_t& s1, const string_t& s2) const
69         { return (0 == strcasecmp(s1.c_str(), s2.c_str())); }
70     };
71 
72     //------------------------------------------------
73     struct recently_used_item
74     {
75         recently_used_item() :
76             is_private_(false)
77         {}
78 
79         recently_used_item(
80             const string_t& uri,
81             const string_t& mime_type,
82             const string_container_t& groups,
83             bool is_private = false) :
84             uri_(uri),
85             mime_type_(mime_type),
86             is_private_(is_private),
87             groups_(groups)
88         {
89             timestamp_ = time(NULL);
90         }
91 
92         void set_uri(const string_t& character)
93         { uri_ = character; }
94 
95         void set_mime_type(const string_t& character)
96         { mime_type_ = character; }
97 
98         void set_timestamp(const string_t& character)
99         {
100             time_t t;
101             if (sscanf(character.c_str(), "%ld", &t) != 1)
102                 timestamp_ = -1;
103             else
104                 timestamp_ = t;
105         }
106 
107         void set_is_private(const string_t& /*character*/)
108         { is_private_ = true; }
109 
110         void set_groups(const string_t& character)
111         { groups_.push_back(character); }
112 
113         void set_nothing(const string_t& /*character*/)
114         {}
115 
116         bool has_groups() const
117         {
118             return !groups_.empty();
119         }
120 
121         bool has_group(const string_t& name) const
122         {
123             string_container_t::const_iterator iter_end = groups_.end();
124             return (has_groups() &&
125                     iter_end != std::find_if(
126                         groups_.begin(), iter_end,
127                         std::bind2nd(str_icase_cmp(), name)));
128         }
129 
130         void write_xml(const recently_used_file& file) const
131         {
132             write_xml_start_tag(TAG_RECENT_ITEM, file, true);
133             write_xml_tag(TAG_URI, uri_, file);
134             write_xml_tag(TAG_MIME_TYPE, mime_type_, file);
135 
136             rtl::OString ts = rtl::OString::valueOf((sal_sSize)timestamp_);
137             write_xml_tag(TAG_TIMESTAMP, ts.getStr(), file);
138 
139             if (is_private_)
140                 write_xml_tag(TAG_PRIVATE, file);
141 
142             if (has_groups())
143             {
144                 write_xml_start_tag(TAG_GROUPS, file, true);
145 
146                 string_container_t::const_iterator iter = groups_.begin();
147                 string_container_t::const_iterator iter_end = groups_.end();
148 
149                 for ( ; iter != iter_end; ++iter)
150                     write_xml_tag(TAG_GROUP, (*iter), file);
151 
152                 write_xml_end_tag(TAG_GROUPS, file);
153             }
154             write_xml_end_tag(TAG_RECENT_ITEM, file);
155         }
156 
157         static rtl::OString escape_content(const string_t &text)
158         {
159             rtl::OStringBuffer aBuf;
160             for (sal_uInt32 i = 0; i < text.length(); i++)
161             {
162 #               define MAP(a,b) case a: aBuf.append(b); break
163                 switch (text[i])
164                 {
165                     MAP ('&',  "&amp;");
166                     MAP ('<',  "&lt;");
167                     MAP ('>',  "&gt;");
168                     MAP ('\'', "&apos;");
169                     MAP ('"',  "&quot;");
170                 default:
171                     aBuf.append(text[i]);
172                     break;
173                 }
174 #               undef MAP
175             }
176             return aBuf.makeStringAndClear();
177         }
178 
179         void write_xml_tag(const string_t& name, const string_t& value, const recently_used_file& file) const
180         {
181             write_xml_start_tag(name, file);
182             rtl::OString escaped = escape_content (value);
183             file.write(escaped.getStr(), escaped.getLength());
184             write_xml_end_tag(name, file);
185         }
186 
187         void write_xml_tag(const string_t& name, const recently_used_file& file) const
188         {
189             file.write("<", 1);
190             file.write(name.c_str(), name.length());
191             file.write("/>\n", 3);
192         }
193 
194         void write_xml_start_tag(const string_t& name, const recently_used_file& file, bool linefeed = false) const
195         {
196             file.write("<", 1);
197             file.write(name.c_str(), name.length());
198             if (linefeed)
199                 file.write(">\n", 2);
200             else
201                 file.write(">", 1);
202         }
203 
204         void write_xml_end_tag(const string_t& name, const recently_used_file& file) const
205         {
206             file.write("</", 2);
207             file.write(name.c_str(), name.length());
208             file.write(">\n", 2);
209         }
210 
211         string_t uri_;
212         string_t mime_type_;
213         time_t timestamp_;
214         bool is_private_;
215         string_container_t groups_;
216     };
217 
218     typedef std::vector<recently_used_item*> recently_used_item_list_t;
219     typedef void (recently_used_item::* SET_COMMAND)(const string_t&);
220 
221     //########################################
222     // thrown if we encounter xml tags that we do not know
223     class unknown_xml_format_exception {};
224 
225     //########################################
226     class recently_used_file_filter : public i_xml_parser_event_handler
227     {
228     public:
229         recently_used_file_filter(recently_used_item_list_t& item_list) :
230             item_(NULL),
231             item_list_(item_list)
232         {
233             named_command_map_[TAG_RECENT_FILES] = &recently_used_item::set_nothing;
234             named_command_map_[TAG_RECENT_ITEM]  = &recently_used_item::set_nothing;
235             named_command_map_[TAG_URI]          = &recently_used_item::set_uri;
236             named_command_map_[TAG_MIME_TYPE]    = &recently_used_item::set_mime_type;
237             named_command_map_[TAG_TIMESTAMP]    = &recently_used_item::set_timestamp;
238             named_command_map_[TAG_PRIVATE]      = &recently_used_item::set_is_private;
239             named_command_map_[TAG_GROUPS]       = &recently_used_item::set_nothing;
240             named_command_map_[TAG_GROUP]        = &recently_used_item::set_groups;
241         }
242 
243         virtual void start_element(
244             const string_t& /*raw_name*/,
245             const string_t& local_name,
246             const xml_tag_attribute_container_t& /*attributes*/)
247         {
248             if ((local_name == TAG_RECENT_ITEM) && (NULL == item_))
249                 item_ = new recently_used_item;
250         }
251 
252         virtual void end_element(const string_t& /*raw_name*/, const string_t& local_name)
253         {
254             // check for end tags w/o start tag
255             if( local_name != TAG_RECENT_FILES && NULL == item_ )
256                 return; // will result in an XML parser error anyway
257 
258             if (named_command_map_.find(local_name) != named_command_map_.end())
259                 (item_->*named_command_map_[local_name])(current_element_);
260             else
261             {
262                 delete item_;
263                 throw unknown_xml_format_exception();
264             }
265 
266             if (local_name == TAG_RECENT_ITEM)
267             {
268                 item_list_.push_back(item_);
269                 item_ = NULL;
270             }
271             current_element_.clear();
272         }
273 
274         virtual void characters(const string_t& character)
275         {
276             if (character != "\n")
277                 current_element_ += character;
278         }
279 
280         virtual void start_document() {}
281         virtual void end_document()   {}
282 
283         virtual void ignore_whitespace(const string_t& /*whitespaces*/)
284         {}
285 
286         virtual void processing_instruction(
287             const string_t& /*target*/, const string_t& /*data*/)
288         {}
289 
290         virtual void comment(const string_t& /*comment*/)
291         {}
292     private:
293         recently_used_item* item_;
294         std::map<string_t, SET_COMMAND> named_command_map_;
295         string_t current_element_;
296         recently_used_item_list_t& item_list_;
297     private:
298         recently_used_file_filter(const recently_used_file_filter&);
299         recently_used_file_filter& operator=(const recently_used_file_filter&);
300     };
301 
302     //------------------------------------------------
303     void read_recently_used_items(
304         recently_used_file& file,
305         recently_used_item_list_t& item_list)
306     {
307         xml_parser xparser;
308         recently_used_file_filter ruff(item_list);
309 
310         xparser.set_document_handler(&ruff);
311 
312         char buff[16384];
313 		while (!file.eof())
314 		{
315         	if (size_t length = file.read(buff, sizeof(buff)))
316                 xparser.parse(buff, length, file.eof());
317 		}
318     }
319 
320     //------------------------------------------------
321     // The file ~/.recently_used shall not contain more than 500
322     // entries (see www.freedesktop.org)
323     const int MAX_RECENTLY_USED_ITEMS = 500;
324 
325     class recent_item_writer
326     {
327     public:
328         recent_item_writer(
329             recently_used_file& file,
330             int max_items_to_write = MAX_RECENTLY_USED_ITEMS) :
331             file_(file),
332             max_items_to_write_(max_items_to_write),
333             items_written_(0)
334         {}
335 
336         void operator() (const recently_used_item* item)
337         {
338             if (items_written_++ < max_items_to_write_)
339                 item->write_xml(file_);
340         }
341     private:
342         recently_used_file& file_;
343         int max_items_to_write_;
344         int items_written_;
345     };
346 
347     //------------------------------------------------
348     const char* XML_HEADER = "<?xml version=\"1.0\"?>\n<RecentFiles>\n";
349     const char* XML_FOOTER = "</RecentFiles>";
350 
351     //------------------------------------------------
352     // assumes that the list is ordered decreasing
353     void write_recently_used_items(
354         recently_used_file& file,
355         recently_used_item_list_t& item_list)
356     {
357         if (!item_list.empty())
358         {
359             file.truncate();
360             file.reset();
361 
362             file.write(XML_HEADER, strlen(XML_HEADER));
363 
364             std::for_each(
365                 item_list.begin(),
366                 item_list.end(),
367                 recent_item_writer(file));
368 
369             file.write(XML_FOOTER, strlen(XML_FOOTER));
370         }
371     }
372 
373     //------------------------------------------------
374     struct delete_recently_used_item
375 	{
376 		void operator() (const recently_used_item* item) const
377 		{ delete item; }
378 	};
379 
380     //------------------------------------------------
381     void recently_used_item_list_clear(recently_used_item_list_t& item_list)
382     {
383         std::for_each(
384             item_list.begin(),
385 			item_list.end(),
386 			delete_recently_used_item());
387 		item_list.clear();
388     }
389 
390     //------------------------------------------------
391     class find_item_predicate
392     {
393     public:
394         find_item_predicate(const string_t& uri) :
395             uri_(uri)
396         {}
397 
398         bool operator() (const recently_used_item* item)
399         { return (item->uri_ == uri_); }
400     private:
401         string_t uri_;
402     };
403 
404     //------------------------------------------------
405     struct greater_recently_used_item
406     {
407         bool operator ()(const recently_used_item* lhs, const recently_used_item* rhs) const
408         { return (lhs->timestamp_ > rhs->timestamp_); }
409     };
410 
411     //------------------------------------------------
412     const char* GROUP_OOO         = "openoffice.org";
413     const char* GROUP_STAR_OFFICE = "staroffice";
414     const char* GROUP_STAR_SUITE  = "starsuite";
415 
416     //------------------------------------------------
417     void recently_used_item_list_add(
418         recently_used_item_list_t& item_list, const rtl::OUString& file_url, const rtl::OUString& mime_type)
419     {
420         rtl::OString f = rtl::OUStringToOString(file_url, RTL_TEXTENCODING_UTF8);
421 
422         recently_used_item_list_t::iterator iter =
423             std::find_if(
424                 item_list.begin(),
425                 item_list.end(),
426                 find_item_predicate(f.getStr()));
427 
428         if (iter != item_list.end())
429         {
430             (*iter)->timestamp_ = time(NULL);
431 
432             if (!(*iter)->has_group(GROUP_OOO))
433                 (*iter)->groups_.push_back(GROUP_OOO);
434             if (!(*iter)->has_group(GROUP_STAR_OFFICE))
435                 (*iter)->groups_.push_back(GROUP_STAR_OFFICE);
436             if (!(*iter)->has_group(GROUP_STAR_SUITE))
437                 (*iter)->groups_.push_back(GROUP_STAR_SUITE);
438         }
439         else
440         {
441             string_container_t groups;
442             groups.push_back(GROUP_OOO);
443             groups.push_back(GROUP_STAR_OFFICE);
444             groups.push_back(GROUP_STAR_SUITE);
445 
446             string_t uri(f.getStr());
447             string_t mimetype(rtl::OUStringToOString(mime_type, osl_getThreadTextEncoding()).getStr());
448 
449             if (mimetype.length() == 0)
450                 mimetype = "application/octet-stream";
451 
452             item_list.push_back(new recently_used_item(uri, mimetype, groups));
453         }
454 
455         // sort decreasing after the timestamp
456         // so that the newest items appear first
457         std::sort(
458             item_list.begin(),
459             item_list.end(),
460             greater_recently_used_item());
461     }
462 
463     //------------------------------------------------
464     struct cleanup_guard
465     {
466         cleanup_guard(recently_used_item_list_t& item_list) :
467             item_list_(item_list)
468         {}
469         ~cleanup_guard()
470         { recently_used_item_list_clear(item_list_); }
471 
472         recently_used_item_list_t& item_list_;
473     };
474 
475 } // namespace private
476 
477 //###########################################
478 /*
479    example (see http::www.freedesktop.org):
480     <?xml version="1.0"?>
481                 <RecentFiles>
482                      <RecentItem>
483                         <URI>file:///home/federico/gedit.txt</URI>
484                         <Mime-Type>text/plain</Mime-Type>
485                         <Timestamp>1046485966</Timestamp>
486                         <Groups>
487                             <Group>gedit</Group>
488                         </Groups>
489                     </RecentItem>
490                     <RecentItem>
491                         <URI>file:///home/federico/gedit-2.2.0.tar.bz2</URI>
492                         <Mime-Type>application/x-bzip</Mime-Type>
493                         <Timestamp>1046209851</Timestamp>
494                         <Private/>
495                         <Groups>
496                         </Groups>
497                     </RecentItem>
498                 </RecentFiles>
499 */
500 
501 extern "C" void add_to_recently_used_file_list(const rtl::OUString& file_url, const rtl::OUString& mime_type)
502 {
503     try
504     {
505         recently_used_file ruf;
506         recently_used_item_list_t item_list;
507         cleanup_guard guard(item_list);
508 
509         read_recently_used_items(ruf, item_list);
510         recently_used_item_list_add(item_list, file_url, mime_type);
511         write_recently_used_items(ruf, item_list);
512     }
513     catch(const char* ex)
514     {
515         OSL_ENSURE(false, ex);
516     }
517     catch(const xml_parser_exception&)
518 	{
519         OSL_ENSURE(false, "XML parser error");
520     }
521     catch(const unknown_xml_format_exception&)
522     {
523         OSL_ENSURE(false, "XML format unknown");
524     }
525 }
526 
527