File: dumpavatars.cc

package info (click to toggle)
signalbackup-tools 20250313.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 3,752 kB
  • sloc: cpp: 47,042; sh: 477; ansic: 399; ruby: 19; makefile: 3
file content (119 lines) | stat: -rw-r--r-- 4,638 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
  Copyright (C) 2021-2024  Selwin van Dijk

  This file is part of signalbackup-tools.

  signalbackup-tools is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  signalbackup-tools is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with signalbackup-tools.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "signalbackup.ih"

#include "../attachmentmetadata/attachmentmetadata.h"

bool SignalBackup::dumpAvatars(std::string const &dir, std::vector<std::string> const &contacts, bool overwrite) const
{
  Logger::message_overwrite("Dumping avatars to dir '", dir, "'...");

  if (!d_database.containsTable("recipient"))
  {
    Logger::error("Database too old, dumping avatars is not (yet) supported, consider a full decrypt by just passing a directory as output");
    return false;
  }

  if (!prepareOutputDirectory(dir, overwrite))
    return false;

#if __cplusplus > 201703L
  for (int count = 0; auto const &avframe : d_avatars)
#else
  int count = 0;
  for (auto const &avframe : d_avatars)
#endif
  {
    ++count;

    Logger::message_overwrite("Dumping avatars to dir '", dir, "'... ", count, "/", d_avatars.size());

    AvatarFrame *af = avframe.second.get();

    SqliteDB::QueryResults results;

    std::string query = "SELECT COALESCE(" + (d_database.tableContainsColumn("recipient", "nickname_joined_name") ? "NULLIF(recipient.nickname_joined_name, ''),"s : ""s) +
      "NULLIF(recipient." + d_recipient_system_joined_name + ", ''), " +
      (d_database.tableContainsColumn("recipient", "profile_joined_name") ? "NULLIF(recipient.profile_joined_name, ''),"s : ""s) +
      "NULLIF(recipient." + d_recipient_profile_given_name + ", ''), NULLIF(groups.title, ''), " +
      (d_database.containsTable("distribution_list") ? "NULLIF(distribution_list.name, ''), " : "") +
      "NULLIF(recipient." + d_recipient_e164 + ", ''), NULLIF(recipient." + d_recipient_aci + ", ''), "
      " recipient._id) AS 'chatpartner' "
      "FROM recipient "
      "LEFT JOIN groups ON recipient.group_id = groups.group_id " +
      (d_database.containsTable("distribution_list") ? "LEFT JOIN distribution_list ON recipient._id = distribution_list.recipient_id " : "") +
      "WHERE recipient._id = ?";

    // if ! limit.empty()
    // query += " AND _id == something, or chatpartner == ''

    if (!d_database.exec(query, af->recipient(),  &results))
      return false;

    if (results.rows() != 1)
    {
      Logger::error("Unexpected number of results: ", results.rows(), " (recipient: ", af->recipient(), ")");
      continue;
    }

    std::string name = results.valueAsString(0, "chatpartner");

    if (!contacts.empty() &&
        std::find(contacts.begin(), contacts.end(), name) == contacts.end())
      continue;

    // get avatar data, to get extension
    std::string extension;
    unsigned char *avatardata = af->attachmentData();
    uint64_t avatarsize = af->attachmentSize();
    AttachmentMetadata amd = AttachmentMetadata::getAttachmentMetaData(std::string(), avatardata, avatarsize, true/*skiphash*/);
    extension = "." + std::string(MimeTypes::getExtension(amd.filetype, "jpg"));
    std::string filename = sanitizeFilename(name + extension);
    if (filename.empty() || filename == extension) // filename was not set in database or was not impossible
                                                   // to sanitize (eg reserved name in windows 'COM1')
      filename = af->recipient() + extension;

    // make filename unique
    while (bepaald::fileOrDirExists(dir + "/" + filename))
      filename += "(2)";

    std::ofstream attachmentstream(dir + "/" + filename, std::ios_base::binary);

    if (!attachmentstream.is_open())
    {
      Logger::error("Failed to open file for writing: ", dir, "/", filename);
      af->clearData();
      continue;
    }

    if (!attachmentstream.write(reinterpret_cast<char *>(af->attachmentData()), af->attachmentSize()))
    {
      Logger::error("Failed to write data to file: ", dir, "/", filename);
      af->clearData();
      continue;
    }

    attachmentstream.close(); // need to close, or the auto-close will change files mtime again.
    af->clearData();
  }

  Logger::message("done.");
  return true;
}