blobiohandler.cpp
1 /*
2  * This file is part of signon
3  *
4  * Copyright (C) 2009-2011 Nokia Corporation.
5  *
6  * Contact: Aurel Popirtac <ext-aurel.popirtac@nokia.com>
7  * Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * version 2.1 as published by the Free Software Foundation.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  */
23 
24 #include "blobiohandler.h"
25 
26 #include <QDBusArgument>
27 #include <QBuffer>
28 #include <QDebug>
29 
30 #include "SignOn/signonplugincommon.h"
31 
32 #define SIGNON_IPC_BUFFER_PAGE_SIZE 16384
33 
34 using namespace SignOn;
35 
36 BlobIOHandler::BlobIOHandler(QIODevice *readChannel,
37  QIODevice *writeChannel,
38  QObject *parent):
39  QObject(parent),
40  m_readChannel(readChannel),
41  m_writeChannel(writeChannel),
42  m_readNotifier(0),
43  m_blobSize(-1)
44 {
45 }
46 
47 void BlobIOHandler::setReadChannelSocketNotifier(QSocketNotifier *notifier)
48 {
49  if (notifier == 0)
50  return;
51 
52  m_readNotifier = notifier;
53 }
54 
55 bool BlobIOHandler::sendData(const QVariantMap &map)
56 {
57  if (m_writeChannel == 0) {
58  TRACE() << "NULL write channel.";
59  return false;
60  }
61 
62  QDataStream stream(m_writeChannel);
63  QByteArray ba = variantMapToByteArray(map);
64  stream << ba.size();
65 
66  QVector<QByteArray> pages = pageByteArray(ba);
67  for (int i = 0; i < pages.count(); ++i)
68  stream << pages[i];
69 
70  return true;
71 }
72 
73 void BlobIOHandler::setReadNotificationEnabled(bool enabled)
74 {
75  if (enabled) {
76  if (m_readNotifier != 0) {
77  m_readNotifier->setEnabled(true);
78  connect(m_readNotifier, SIGNAL(activated(int)),
79  this, SLOT(readBlob()));
80  } else {
81  connect(m_readChannel, SIGNAL(readyRead()),
82  this, SLOT(readBlob()));
83  }
84  } else {
85  if (m_readNotifier != 0) {
86  disconnect(m_readNotifier, SIGNAL(activated(int)),
87  this, SLOT(readBlob()));
88  m_readNotifier->setEnabled(false);
89  } else {
90  disconnect(m_readChannel, SIGNAL(readyRead()),
91  this, SLOT(readBlob()));
92  }
93  }
94 }
95 
96 void BlobIOHandler::receiveData(int expectedDataSize)
97 {
98  m_blobBuffer.clear();
99  m_blobSize = expectedDataSize;
100 
101  //Enable read notification only if more than 1 BLOB page is to be received
102  //This does not allow duplicate read attempts if only 1 page is available
103  if (m_blobSize > SIGNON_IPC_BUFFER_PAGE_SIZE)
104  setReadNotificationEnabled(true);
105 
106  readBlob();
107 }
108 
109 void BlobIOHandler::readBlob()
110 {
111  QDataStream in(m_readChannel);
112 
113  QByteArray fractionBa;
114  in >> fractionBa;
115  m_blobBuffer.append(fractionBa);
116 
117  //Avoid infinite loops if the other party behaves badly
118  if ((fractionBa.size() == 0) && (m_blobBuffer.size() < m_blobSize)) {
119  setReadNotificationEnabled(false);
120  emit error();
121  return;
122  }
123 
124  if (m_blobBuffer.size() == m_blobSize) {
125  QVariantMap sessionDataMap;
126  sessionDataMap = byteArrayToVariantMap(m_blobBuffer);
127 
128  if (m_blobSize > SIGNON_IPC_BUFFER_PAGE_SIZE)
129  setReadNotificationEnabled(false);
130 
131  emit dataReceived(sessionDataMap);
132  }
133 }
134 
135 QVariantMap expandDBusArgumentValue(const QVariant &value, bool *success)
136 {
137  // first, convert the QDBusArgument to a map
138  QDBusArgument dbusValue = value.value<QDBusArgument>();
139  QVariantMap converted;
140  if (dbusValue.currentType() == QDBusArgument::MapType) {
141  //Assume that all maps are a{sv}
142  converted = qdbus_cast<QVariantMap>(dbusValue);
143  } else {
144  *success = false;
145  return QVariantMap();
146  }
147 
148  // Then, check each value of the converted map
149  // and if any QDBusArgument is a value, convert that.
150  QVariantMap returnValue;
151  QVariantMap::const_iterator i;
152  for (i = converted.constBegin(); i != converted.constEnd(); ++i) {
153  if (qstrcmp(i.value().typeName(), "QDBusArgument") == 0) {
154  QVariantMap convertedValue = expandDBusArgumentValue(i.value(), success);
155  if (success == false) {
156  //bail out to prevent error in serialization
157  return QVariantMap();
158  }
159  returnValue.insert(i.key(), convertedValue);
160  } else {
161  returnValue.insert(i.key(), i.value());
162  }
163  }
164 
165  return returnValue;
166 }
167 
168 static QVariantMap filterOutComplexTypes(const QVariantMap &map)
169 {
170  QVariantMap filteredMap;
171  QVariantMap::const_iterator i;
172  for (i = map.constBegin(); i != map.constEnd(); i++) {
173  if (qstrcmp(i.value().typeName(), "QDBusArgument") == 0) {
174  bool success = true;
175  QVariantMap convertedMap = expandDBusArgumentValue(i.value(), &success);
176  if (success == false) {
177  /* QDBusArgument are complex types; there is no QDataStream
178  * serialization for them, so keeping them in the map would
179  * make the serialization fail for the whole map, if we are
180  * unable to convert to a QVariantMap.
181  * Therefore, skip them. */
182  BLAME() << "Found non-map QDBusArgument in data; skipping.";
183  continue;
184  }
185  filteredMap.insert(i.key(), convertedMap);
186  } else {
187  filteredMap.insert(i.key(), i.value());
188  }
189  }
190  return filteredMap;
191 }
192 
193 QByteArray BlobIOHandler::variantMapToByteArray(const QVariantMap &map)
194 {
195  QBuffer buffer;
196  if (!buffer.open(QIODevice::WriteOnly))
197  BLAME() << "Buffer opening failed.";
198 
199  QDataStream stream(&buffer);
200  stream << filterOutComplexTypes(map);
201  buffer.close();
202 
203  return buffer.data();
204 }
205 
206 QVariantMap BlobIOHandler::byteArrayToVariantMap(const QByteArray &array)
207 {
208  QByteArray nonConst = array;
209  QBuffer buffer(&nonConst);
210  if (!buffer.open(QIODevice::ReadOnly))
211  BLAME() << "Buffer opening failed.";
212 
213  buffer.reset();
214  QDataStream stream(&buffer);
215  QVariantMap map;
216  stream >> map;
217  buffer.close();
218 
219  return map;
220 }
221 
222 QVector<QByteArray> BlobIOHandler::pageByteArray(const QByteArray &array)
223 {
224  QVector<QByteArray> dataPages;
225  QByteArray ba = array;
226  QBuffer pagingBuffer(&ba);
227 
228  if (!pagingBuffer.open(QIODevice::ReadOnly))
229  BLAME() << "Error while paging BLOB. Buffer opening failed.";
230 
231  while (!pagingBuffer.atEnd()) {
232  QByteArray page = pagingBuffer.read(SIGNON_IPC_BUFFER_PAGE_SIZE);
233  dataPages.append(page);
234  }
235  pagingBuffer.close();
236 
237  return dataPages;
238 }