| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# SMS.pm |
|
2
|
|
|
|
|
|
|
# |
|
3
|
|
|
|
|
|
|
# Perl module for reading, manipulating, and writing the .pdb files |
|
4
|
|
|
|
|
|
|
# used by Handspring SMS application on PalmOS devices such as |
|
5
|
|
|
|
|
|
|
# Handspring Treo 270. |
|
6
|
|
|
|
|
|
|
# |
|
7
|
|
|
|
|
|
|
# This code is provided "as is" with no warranty. The exact terms |
|
8
|
|
|
|
|
|
|
# under which you may use and (re)distribute it are detailed |
|
9
|
|
|
|
|
|
|
# in the GNU General Public License, in the file COPYING. |
|
10
|
|
|
|
|
|
|
# |
|
11
|
|
|
|
|
|
|
# Copyright (C) 2005 Free Software Foundation, Inc. |
|
12
|
|
|
|
|
|
|
# |
|
13
|
|
|
|
|
|
|
# Written by Lorenzo Cappelletti |
|
14
|
|
|
|
|
|
|
# |
|
15
|
|
|
|
|
|
|
# |
|
16
|
|
|
|
|
|
|
# $Id: SMS.pm,v 1.1 2009/01/10 16:17:59 drhyde Exp $ |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
package Palm::SMS; |
|
19
|
|
|
|
|
|
|
|
|
20
|
1
|
|
|
1
|
|
36916
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
43
|
|
|
21
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
31
|
|
|
22
|
1
|
|
|
1
|
|
924
|
use Palm::Raw(); |
|
|
1
|
|
|
|
|
466
|
|
|
|
1
|
|
|
|
|
24
|
|
|
23
|
1
|
|
|
1
|
|
5
|
use vars qw($VERSION @ISA @folders); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
1522
|
|
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
$VERSION = 0.03; |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
@ISA = qw(Palm::Raw); |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 NAME |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Palm::SMS - parse SMS database files |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
use Palm::PDB; |
|
36
|
|
|
|
|
|
|
use Palm::SMS; |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my $pdb = new Palm::PDB; |
|
39
|
|
|
|
|
|
|
$pdb->Load("sms.pdb"); |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
my $record = $pdb->{records}[0]; |
|
42
|
|
|
|
|
|
|
print "$record->{text}\n"; |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
The SMS PDB handler is a helper class for the Palm::PDB module. It is |
|
47
|
|
|
|
|
|
|
intended for reading, manipulating, and writing the .pdb files used by |
|
48
|
|
|
|
|
|
|
Handspring SMS application on PalmOS devices such as Handspring Treo |
|
49
|
|
|
|
|
|
|
270. |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
Palm::SMS module is the result of a reverse engineering attempt of |
|
52
|
|
|
|
|
|
|
trasforming a Handspring Treo 270 SMS PDB file into a plain text file. |
|
53
|
|
|
|
|
|
|
The PDB test file was produced by Handspring's application SMS |
|
54
|
|
|
|
|
|
|
v. 3.5H. |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
Due to lack of knowledge about how PDB files work and what format SMS |
|
57
|
|
|
|
|
|
|
database files conform to, at present this module is not suitable for |
|
58
|
|
|
|
|
|
|
from-scratch SMS generation. Conversely, you may find it extremely |
|
59
|
|
|
|
|
|
|
useful if you intend to extract SMS messages from merged PDB files and |
|
60
|
|
|
|
|
|
|
convert them to a human readable form. |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head2 Fields |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
$record = $pdb->{records}[N]; |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
$record->{name} |
|
67
|
|
|
|
|
|
|
$record->{firstName} |
|
68
|
|
|
|
|
|
|
$record->{phone} |
|
69
|
|
|
|
|
|
|
$record->{folder} |
|
70
|
|
|
|
|
|
|
$record->{timestamp} |
|
71
|
|
|
|
|
|
|
$record->{text} |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
$record->{smsh} |
|
74
|
|
|
|
|
|
|
$record->{unknown1} |
|
75
|
|
|
|
|
|
|
$record->{unknown2} |
|
76
|
|
|
|
|
|
|
$record->{unknown3} |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
The fields provided for each record are the following: |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=over |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=item name |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
A string containing the name of the person who wrote the message. |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=item firstname |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
A string containing the first name of the person who wrote the |
|
89
|
|
|
|
|
|
|
message. |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=item phone |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
A string containing the phone number. |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=item timestamp |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
An integer which represents the number of seconds elapsed from Unix |
|
98
|
|
|
|
|
|
|
epoch to message creation time. |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
It is worth noticing that there is no way of retrieving neither the TZ |
|
101
|
|
|
|
|
|
|
nor the DST out of data stored in the PDB file. This timestamp always |
|
102
|
|
|
|
|
|
|
expresses the time of your handheld's clock at message creation time. |
|
103
|
|
|
|
|
|
|
Hence, I suggest passing the value C as third argument to |
|
104
|
|
|
|
|
|
|
L's time2str() to get the right timestamp |
|
105
|
|
|
|
|
|
|
rappresentation: |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
use Date::Format; |
|
108
|
|
|
|
|
|
|
... |
|
109
|
|
|
|
|
|
|
$timestamp = time2str("%T %%Z", $record->{timestamp}, "GMT"); |
|
110
|
|
|
|
|
|
|
$timestamp = time2str($timestamp, $record->{timestamp} ); |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
=item folder |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
An integer which represents in which folder the message was stored. |
|
115
|
|
|
|
|
|
|
English folder names (such as I or I) are available as |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
$Palm::SMS::folders[$record->{folder}]; |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=item text |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
A string containing the message body. |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=back |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
The module provides additional fields which will probably be less |
|
126
|
|
|
|
|
|
|
commonly used. |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=over |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=item smsh |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
This string of four bytes ("I") is present at the start of each |
|
133
|
|
|
|
|
|
|
record. |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
=item unknown1 |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=item unknown2 |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=item unknown3 |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
These fields contain a chunk of bytes whose function is not yet known. |
|
142
|
|
|
|
|
|
|
Please, refer to the L method. |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=back |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
=head2 Fields for the Treo 680 |
|
147
|
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
The Treo 680 uses different software, and Palm have not documented its |
|
149
|
|
|
|
|
|
|
message format. Consequently only some information is available, and |
|
150
|
|
|
|
|
|
|
some records that are extracted are extracted incorrectly. The |
|
151
|
|
|
|
|
|
|
following fields are available, and have been reverse-engineered from |
|
152
|
|
|
|
|
|
|
a single sample database. Consequently, you should treat their |
|
153
|
|
|
|
|
|
|
values with suspicion. |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Treo 680 databases are read-only. |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
If the Palm::Treo680MessagesDB module is available, then that will be |
|
158
|
|
|
|
|
|
|
used instead. Over time, that module is intended to do a better job, |
|
159
|
|
|
|
|
|
|
as and when I figure out new bits of the puzzle. |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=over |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=item device |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
This will always be "Treo 680" |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item direction |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
The direction of the SMS relative to your phone. This will be either |
|
170
|
|
|
|
|
|
|
'inbound' or 'outbound' |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=item number |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
The other party's phone number (same as for any other device) |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=item name |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
The other party's name (same as for any other device) |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=item text |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
The text of the message (same as for any other device) |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item type |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
A number representing the type of message. If this is 'unknown' then |
|
187
|
|
|
|
|
|
|
none of the above fields will be populated. |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=item rawdata |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
The raw binary data of the record |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=back |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=head1 METHODS |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=cut |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
@folders = ( # SMS folder names |
|
200
|
|
|
|
|
|
|
"Inbox", |
|
201
|
|
|
|
|
|
|
"Sent", |
|
202
|
|
|
|
|
|
|
"Pending", # guessed |
|
203
|
|
|
|
|
|
|
); |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
my $EPOCH_1904 = 2082844800; # Difference between Palm's |
|
206
|
|
|
|
|
|
|
# epoch (Jan. 1, 1904) and |
|
207
|
|
|
|
|
|
|
# Unix's epoch (Jan. 1, 1970), |
|
208
|
|
|
|
|
|
|
# in seconds. |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub import { |
|
211
|
1
|
|
|
1
|
|
15
|
&Palm::PDB::RegisterPDBHandlers(__PACKAGE__, |
|
212
|
|
|
|
|
|
|
[ "SMS!", "DATA" ], |
|
213
|
|
|
|
|
|
|
); |
|
214
|
1
|
|
|
|
|
22
|
&Palm::PDB::RegisterPDBHandlers(__PACKAGE__, |
|
215
|
|
|
|
|
|
|
[ "HsCh", "SMct" ], |
|
216
|
|
|
|
|
|
|
); |
|
217
|
1
|
50
|
|
1
|
|
510
|
eval "use Palm::Treo680MessagesDB" || |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
1
|
|
|
|
|
91
|
|
|
218
|
|
|
|
|
|
|
&Palm::PDB::RegisterPDBHandlers(__PACKAGE__, # GSM Treo 680 |
|
219
|
|
|
|
|
|
|
[ "MsSt", "MsDb" ], # Messaging app v 2.6.1 |
|
220
|
|
|
|
|
|
|
); |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head2 new |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
$pdb = new Palm::SMS; |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
Creates a new PDB, initialized with the various Palm::SMS fields |
|
228
|
|
|
|
|
|
|
and an empty record list. |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
Use this method if you're creating a SMS PDB from scratch. |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=cut |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
#' |
|
235
|
|
|
|
|
|
|
sub new { |
|
236
|
0
|
|
|
0
|
1
|
0
|
my $classname = shift; |
|
237
|
0
|
|
|
|
|
0
|
my $self = $classname->SUPER::new(@_); # no need to rebless |
|
238
|
0
|
|
|
|
|
0
|
$self->{name} = "SMS Messages"; # default |
|
239
|
0
|
|
|
|
|
0
|
$self->{creator} = "SMS!"; |
|
240
|
0
|
|
|
|
|
0
|
$self->{type} = "DATA"; |
|
241
|
0
|
|
|
|
|
0
|
$self->{attributes}{resource} = 0; # not a resource db |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
|
|
244
|
0
|
|
|
|
|
0
|
$self->{sort} = undef; # empty sort block |
|
245
|
|
|
|
|
|
|
|
|
246
|
0
|
|
|
|
|
0
|
$self->{records} = []; # empty list of records |
|
247
|
|
|
|
|
|
|
|
|
248
|
0
|
|
|
|
|
0
|
return $self; |
|
249
|
|
|
|
|
|
|
} |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head2 new_Record |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
$record = $pdb->new_Record; |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
$record->{phone} = "1234567890"; |
|
256
|
|
|
|
|
|
|
$record->{folder} = 1; |
|
257
|
|
|
|
|
|
|
... |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
$phone = $record->{phone}; |
|
260
|
|
|
|
|
|
|
$folder = $Palm::SMS::folders[$record->{folder}]; |
|
261
|
|
|
|
|
|
|
... |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
Creates a new SMS record, with blank values for all of the fields. |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
C does B add the new record to C<$pdb>. For that, |
|
266
|
|
|
|
|
|
|
you want C<$pdb-Eappend_Record>. |
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
Default field values are: |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
name : undef |
|
271
|
|
|
|
|
|
|
firstName: undef |
|
272
|
|
|
|
|
|
|
phone : undef |
|
273
|
|
|
|
|
|
|
timestamp: localtime() |
|
274
|
|
|
|
|
|
|
folder : 1 |
|
275
|
|
|
|
|
|
|
text : undef |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
smsh : "SMSh" |
|
278
|
|
|
|
|
|
|
unknown1 : undef |
|
279
|
|
|
|
|
|
|
unknown2 : undef |
|
280
|
|
|
|
|
|
|
unknown3 : undef |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=cut |
|
283
|
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
sub new_Record { |
|
285
|
0
|
|
|
0
|
1
|
0
|
my $classname = shift; |
|
286
|
0
|
|
|
|
|
0
|
my $retval = $classname->SUPER::new_Record(@_); |
|
287
|
|
|
|
|
|
|
|
|
288
|
0
|
|
|
|
|
0
|
$retval->{name} = undef; |
|
289
|
0
|
|
|
|
|
0
|
$retval->{firstName} = undef; |
|
290
|
0
|
|
|
|
|
0
|
$retval->{phone} = undef; |
|
291
|
0
|
|
|
|
|
0
|
$retval->{timestamp} = localtime; |
|
292
|
0
|
|
|
|
|
0
|
$retval->{folder} = 1; |
|
293
|
0
|
|
|
|
|
0
|
$retval->{text} = undef; |
|
294
|
|
|
|
|
|
|
|
|
295
|
0
|
|
|
|
|
0
|
$retval->{smsh} = "SMSh"; |
|
296
|
0
|
|
|
|
|
0
|
$retval->{unknown1} = undef; |
|
297
|
0
|
|
|
|
|
0
|
$retval->{unknown2} = undef; |
|
298
|
0
|
|
|
|
|
0
|
$retval->{unknown2} = undef; |
|
299
|
|
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
0
|
return $retval; |
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=head2 ParseRecord |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
ParseRecord() returns a parsed representation of the record, typically |
|
306
|
|
|
|
|
|
|
as a reference to a record object or anonymous hash. It is |
|
307
|
|
|
|
|
|
|
automatically called from within L and, as such, is not |
|
308
|
|
|
|
|
|
|
intented to be used directly from applications. |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
The record structure which an SMS posses is: |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
smsh : 4-byte ASCII string |
|
313
|
|
|
|
|
|
|
unknown1 : 2-byte data whose function is unknown |
|
314
|
|
|
|
|
|
|
timestamp: 32-bit, big-endian, unsigned integer rappresenting |
|
315
|
|
|
|
|
|
|
the number of seconds since 1904 |
|
316
|
|
|
|
|
|
|
unknown2 : 26-byte data whose function is unknown |
|
317
|
|
|
|
|
|
|
phone : Null terminated string |
|
318
|
|
|
|
|
|
|
name : Null terminated string |
|
319
|
|
|
|
|
|
|
firstname: Null terminated string |
|
320
|
|
|
|
|
|
|
unknown3 : 16-byte data whose function is unknown |
|
321
|
|
|
|
|
|
|
text : Null terminated (sent messages only) string |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
I field value is copied from I field which is |
|
324
|
|
|
|
|
|
|
computed by Palm::PDB and then delted since there is no application |
|
325
|
|
|
|
|
|
|
info block (see L) in the PDB file. |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
I is empty for messages belonging to category 1 (folder |
|
328
|
|
|
|
|
|
|
I). |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
It is worth noticing that length, offset, and even availability of |
|
331
|
|
|
|
|
|
|
I data are not preserved between module version when their |
|
332
|
|
|
|
|
|
|
meaning becomes clear. |
|
333
|
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
=cut |
|
335
|
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub ParseRecord { |
|
337
|
2
|
|
|
2
|
1
|
1787
|
my $self = shift; |
|
338
|
2
|
|
|
|
|
7
|
my %record = @_; |
|
339
|
2
|
|
|
|
|
4
|
my @unpack; |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
my $smsh; # each record starts with "SMSh": SMS handler? |
|
342
|
|
|
|
|
|
|
# not on Treo 680 |
|
343
|
0
|
|
|
|
|
0
|
my $unknown1; |
|
344
|
0
|
|
|
|
|
0
|
my $timestamp; |
|
345
|
0
|
|
|
|
|
0
|
my $unknown2; |
|
346
|
0
|
|
|
|
|
0
|
my $name; |
|
347
|
0
|
|
|
|
|
0
|
my $firstName; |
|
348
|
0
|
|
|
|
|
0
|
my $unknown3; |
|
349
|
0
|
|
|
|
|
0
|
my $phone; |
|
350
|
0
|
|
|
|
|
0
|
my $folder; |
|
351
|
0
|
|
|
|
|
0
|
my $text; |
|
352
|
|
|
|
|
|
|
|
|
353
|
2
|
50
|
|
|
|
16
|
if ($self->{creator} eq "HsCh") { |
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
354
|
0
|
|
|
|
|
0
|
($smsh, |
|
355
|
|
|
|
|
|
|
$unknown1, |
|
356
|
|
|
|
|
|
|
$timestamp, |
|
357
|
|
|
|
|
|
|
$unknown2, |
|
358
|
|
|
|
|
|
|
$text, |
|
359
|
|
|
|
|
|
|
) = unpack("a2 A4 N a24 Z* a*", $record{data}); |
|
360
|
0
|
0
|
|
|
|
0
|
if ($timestamp eq "") {$timestamp=$EPOCH_1904;} |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
361
|
|
|
|
|
|
|
else {$timestamp -= 14400;} |
|
362
|
0
|
0
|
|
|
|
0
|
if ($smsh eq "\0\0" ) { $phone="Target"; } |
|
|
0
|
|
|
|
|
0
|
|
|
363
|
0
|
|
|
|
|
0
|
else { $phone="Me"; } |
|
364
|
|
|
|
|
|
|
} elsif($self->{creator} eq 'MsSt') { # Treo 680 |
|
365
|
0
|
|
|
|
|
0
|
my $buf = $record{data}; |
|
366
|
0
|
|
|
|
|
0
|
my $type = 256 * ord(substr($buf, 10, 1)) + ord(substr($buf, 11, 1)); |
|
367
|
0
|
|
|
|
|
0
|
my($dir, $num, $name, $msg) = ('', '', '', ''); |
|
368
|
0
|
0
|
0
|
|
|
0
|
if($type == 0x400C || $type == 0x4009) { # 4009 not used by 680? |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
369
|
0
|
0
|
|
|
|
0
|
$dir = ($type == 0x400C) ? 'inbound' : 'outbound'; |
|
370
|
0
|
|
|
|
|
0
|
($num, $name, $msg) = (split(/\00+/, substr($buf, 34)))[0, 1, 3]; |
|
371
|
0
|
|
|
|
|
0
|
$msg = substr($msg, 1); |
|
372
|
|
|
|
|
|
|
} elsif($type == 0) { |
|
373
|
0
|
|
|
|
|
0
|
$dir = 'outbound'; |
|
374
|
0
|
|
|
|
|
0
|
($num, $name, $msg) = split(/\00+/, substr($buf, 0x4C), 3); |
|
375
|
0
|
|
|
|
|
0
|
$msg =~ s/^.{9}//s; |
|
376
|
0
|
|
|
|
|
0
|
$msg =~ s/\00.*$//s; |
|
377
|
|
|
|
|
|
|
} elsif($type == 0x0002) { |
|
378
|
0
|
|
|
|
|
0
|
$dir = 'outbound'; |
|
379
|
0
|
|
|
|
|
0
|
($num, $name, $msg) = split(/\00+/, substr($buf, 0x46), 3); |
|
380
|
0
|
|
|
|
|
0
|
$msg =~ s/^.Trsm....//s; |
|
381
|
0
|
|
|
|
|
0
|
$msg =~ s/\00.*$//s; |
|
382
|
|
|
|
|
|
|
} else { |
|
383
|
0
|
|
|
|
|
0
|
$type = 'unknown'; |
|
384
|
|
|
|
|
|
|
} |
|
385
|
0
|
|
|
|
|
0
|
@record{qw(device type direction phone name text rawdata)} = |
|
386
|
|
|
|
|
|
|
("Treo 680", $type, $dir, $num, $name, $msg, $buf); |
|
387
|
|
|
|
|
|
|
} elsif ($record{category} == 0) { |
|
388
|
|
|
|
|
|
|
### Inbox folder ### |
|
389
|
1
|
|
|
|
|
2
|
my $nameFlag; # whether name and firstName are available |
|
390
|
|
|
|
|
|
|
my $extra; # temporary string |
|
391
|
|
|
|
|
|
|
|
|
392
|
1
|
|
|
|
|
7
|
($smsh, |
|
393
|
|
|
|
|
|
|
$unknown1, |
|
394
|
|
|
|
|
|
|
$timestamp, |
|
395
|
|
|
|
|
|
|
$unknown2, |
|
396
|
|
|
|
|
|
|
$phone, |
|
397
|
|
|
|
|
|
|
$extra, |
|
398
|
|
|
|
|
|
|
) = unpack("A4 a2 N a26 Z* a*", $record{data}); |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# unknown2 tells whether name and firstName are available |
|
401
|
1
|
|
|
|
|
4
|
$nameFlag = unpack("x7 H", $unknown2); |
|
402
|
1
|
50
|
|
|
|
5
|
if ($nameFlag eq "4") { |
|
403
|
1
|
|
|
|
|
5
|
($name, |
|
404
|
|
|
|
|
|
|
$firstName, |
|
405
|
|
|
|
|
|
|
$extra, |
|
406
|
|
|
|
|
|
|
) = unpack("Z* Z* a*", $extra); |
|
407
|
|
|
|
|
|
|
} |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# $extra's head contains unknown3 followed by "\d\0" |
|
410
|
1
|
|
|
|
|
12
|
($unknown3, $text) = $extra =~ m/(.*?\d\0)([^\0]+)$/; |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
} elsif ($record{category} == 1) { |
|
413
|
|
|
|
|
|
|
### Sent folder ### |
|
414
|
1
|
|
|
|
|
1
|
my $unpack; |
|
415
|
|
|
|
|
|
|
|
|
416
|
1
|
|
|
|
|
23
|
($smsh, |
|
417
|
|
|
|
|
|
|
$unknown1, |
|
418
|
|
|
|
|
|
|
$timestamp, |
|
419
|
|
|
|
|
|
|
$unknown2, |
|
420
|
|
|
|
|
|
|
$phone, |
|
421
|
|
|
|
|
|
|
$name, |
|
422
|
|
|
|
|
|
|
$firstName, |
|
423
|
|
|
|
|
|
|
$text, |
|
424
|
|
|
|
|
|
|
) = unpack("A4 a2 N a26 Z* Z* Z* Z*", $record{data}); |
|
425
|
1
|
|
|
|
|
4
|
$unknown3 = ""; |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
} elsif ($record{category} == 2) { |
|
428
|
|
|
|
|
|
|
### Pending folder ### |
|
429
|
0
|
|
|
|
|
0
|
die "Never tried to parse a message from Pending folder"; |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
} else { |
|
432
|
0
|
|
|
|
|
0
|
die "Unknown category"; |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
} |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
# Work out common extracted values |
|
437
|
2
|
|
|
|
|
3
|
$timestamp -= $EPOCH_1904; |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
# Assign extracted values to record |
|
440
|
2
|
50
|
|
|
|
7
|
if($self->{creator} ne 'MsSt') { |
|
441
|
2
|
|
|
|
|
3
|
$record{name} = $name; |
|
442
|
2
|
|
|
|
|
4
|
$record{firstName} = $firstName; |
|
443
|
2
|
|
|
|
|
6
|
$record{phone} = $phone; |
|
444
|
2
|
|
|
|
|
4
|
$record{timestamp} = $timestamp; |
|
445
|
2
|
|
|
|
|
5
|
$record{folder} = $record{category}; |
|
446
|
2
|
|
|
|
|
3
|
$record{text} = $text; |
|
447
|
|
|
|
|
|
|
|
|
448
|
2
|
|
|
|
|
5
|
$record{smsh} = $smsh; |
|
449
|
2
|
|
|
|
|
4
|
$record{unknown1} = $unknown1; |
|
450
|
2
|
|
|
|
|
9
|
$record{unknown2} = $unknown2; |
|
451
|
2
|
|
|
|
|
5
|
$record{unknown3} = $unknown3; |
|
452
|
|
|
|
|
|
|
} |
|
453
|
|
|
|
|
|
|
|
|
454
|
2
|
|
|
|
|
4
|
delete $record{data}; |
|
455
|
|
|
|
|
|
|
|
|
456
|
2
|
|
|
|
|
7
|
return \%record; |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
=head2 PackRecord |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
This is the converse of L. PackRecord() |
|
462
|
|
|
|
|
|
|
takes a record as returned by ParseRecord() and returns a string of |
|
463
|
|
|
|
|
|
|
raw data that can be written to the database file. As |
|
464
|
|
|
|
|
|
|
L, this function is not intended to be |
|
465
|
|
|
|
|
|
|
used directly from applications. |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
Because there are chunk of record data whose function is unknown (see |
|
468
|
|
|
|
|
|
|
L), this method may produce an invalid |
|
469
|
|
|
|
|
|
|
result, expecially when passed record was created from scratch via |
|
470
|
|
|
|
|
|
|
L. |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
This method is granted to work if the record being packed has been |
|
473
|
|
|
|
|
|
|
unpacked from an existing PDB and no information has been added. |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=cut |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub PackRecord { |
|
478
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
|
479
|
0
|
|
|
|
|
|
my $record = shift; |
|
480
|
0
|
|
|
|
|
|
my $retval; |
|
481
|
|
|
|
|
|
|
my $pack; |
|
482
|
|
|
|
|
|
|
|
|
483
|
0
|
|
|
|
|
|
$pack = "A4 a2 N a26 Z*"; |
|
484
|
|
|
|
|
|
|
|
|
485
|
0
|
0
|
|
|
|
|
if ($record->{folder} == 0) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
### Inbox folder ### |
|
487
|
0
|
0
|
0
|
|
|
|
if (not ($record->{name} or $record->{firstName})) { |
|
488
|
0
|
|
|
|
|
|
$pack .= " a* A*"; |
|
489
|
0
|
|
|
|
|
|
$retval = pack($pack, |
|
490
|
|
|
|
|
|
|
$record->{smsh}, |
|
491
|
|
|
|
|
|
|
$record->{unknown1}, |
|
492
|
|
|
|
|
|
|
$record->{timestamp} + $EPOCH_1904, |
|
493
|
|
|
|
|
|
|
$record->{unknown2}, |
|
494
|
|
|
|
|
|
|
$record->{phone}, |
|
495
|
|
|
|
|
|
|
$record->{unknown3}, |
|
496
|
|
|
|
|
|
|
$record->{text}); |
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
} else { |
|
499
|
0
|
|
|
|
|
|
$pack .= " Z* Z* a* A*"; |
|
500
|
0
|
|
|
|
|
|
$retval = pack($pack, |
|
501
|
|
|
|
|
|
|
$record->{smsh}, |
|
502
|
|
|
|
|
|
|
$record->{unknown1}, |
|
503
|
|
|
|
|
|
|
$record->{timestamp} + $EPOCH_1904, |
|
504
|
|
|
|
|
|
|
$record->{unknown2}, |
|
505
|
|
|
|
|
|
|
$record->{phone}, |
|
506
|
|
|
|
|
|
|
$record->{name}, |
|
507
|
|
|
|
|
|
|
$record->{firstName}, |
|
508
|
|
|
|
|
|
|
$record->{unknown3}, |
|
509
|
|
|
|
|
|
|
$record->{text}); |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
} |
|
512
|
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
} elsif ($record->{folder} ==1) { |
|
514
|
|
|
|
|
|
|
### Sent folder ### |
|
515
|
0
|
|
|
|
|
|
$pack .= " Z* Z* Z*"; |
|
516
|
0
|
|
|
|
|
|
$retval = pack($pack, |
|
517
|
|
|
|
|
|
|
$record->{smsh}, |
|
518
|
|
|
|
|
|
|
$record->{unknown1}, |
|
519
|
|
|
|
|
|
|
$record->{timestamp} + $EPOCH_1904, |
|
520
|
|
|
|
|
|
|
$record->{unknown2}, |
|
521
|
|
|
|
|
|
|
$record->{phone}, |
|
522
|
|
|
|
|
|
|
$record->{name}, |
|
523
|
|
|
|
|
|
|
$record->{firstName}, |
|
524
|
|
|
|
|
|
|
$record->{text}); |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
} elsif ($record->{folder} == 2) { |
|
527
|
|
|
|
|
|
|
### Pending folder ### |
|
528
|
0
|
|
|
|
|
|
die "Never tried to pack a message to Pending folder"; |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
} else { |
|
531
|
0
|
|
|
|
|
|
die "Unknown category"; |
|
532
|
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
} |
|
534
|
|
|
|
|
|
|
|
|
535
|
0
|
|
|
|
|
|
return $retval; |
|
536
|
|
|
|
|
|
|
} |
|
537
|
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
1; |
|
539
|
|
|
|
|
|
|
__END__ |