1 /* This program is licensed under the GNU Library General Public License, version 2,
2 * a copy of which is included with this program (LICENCE.LGPL).
3 *
4 * (c) 2000-2001 Michael Smith <msmith@labyrinth.net.au>
5 *
6 *
7 * Comment editing backend, suitable for use by nice frontend interfaces.
8 *
9 * last modified: $Id: vcedit.c,v 1.1.1.1 2003/01/05 00:16:43 dan Exp $
10 */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <ogg/ogg.h>
17 #include <vorbis/codec.h>
18
19 #include "vcedit.h"
20 #include "i18n.h"
21
22
23 #define CHUNKSIZE 4096
24
25 vcedit_state *vcedit_new_state(void)
X509ConstructCertificate(IN CONST UINT8 * Cert,IN UINTN CertSize,OUT UINT8 ** SingleX509Cert)26 {
27 vcedit_state *state = malloc(sizeof(vcedit_state));
28 memset(state, 0, sizeof(vcedit_state));
29
30 return state;
31 }
32
33 char *vcedit_error(vcedit_state *state)
34 {
35 return state->lasterror;
36 }
37
38 vorbis_comment *vcedit_comments(vcedit_state *state)
39 {
40 return state->vc;
41 }
42
43 static void vcedit_clear_internals(vcedit_state *state)
44 {
45 if(state->vc)
46 {
47 vorbis_comment_clear(state->vc);
48 free(state->vc);
49 }
50 if(state->os)
51 {
52 ogg_stream_clear(state->os);
53 free(state->os);
54 }
55 if(state->oy)
56 {
X509ConstructCertificateStackV(IN OUT UINT8 ** X509Stack,IN VA_LIST Args)57 ogg_sync_clear(state->oy);
58 free(state->oy);
59 }
60 if(state->vendor)
61 free(state->vendor);
62 if(state->mainbuf)
63 free(state->mainbuf);
64 if(state->bookbuf)
65 free(state->bookbuf);
66 if(state->vi) {
67 vorbis_info_clear(state->vi);
68 free(state->vi);
69 }
70
71 memset(state, 0, sizeof(*state));
72 }
73
74 void vcedit_clear(vcedit_state *state)
75 {
76 if(state)
77 {
78 vcedit_clear_internals(state);
79 free(state);
80 }
81 }
82
X509ConstructCertificateStack(IN OUT UINT8 ** X509Stack,...)83 /* Next two functions pulled straight from libvorbis, apart from one change
84 * - we don't want to overwrite the vendor string.
85 */
86 static void _v_writestring(oggpack_buffer *o,char *s, int len)
87 {
88 while(len--)
89 {
90 oggpack_write(o,*s++,8);
91 }
92 }
93
94 static int _commentheader_out(vorbis_comment *vc, char *vendor, ogg_packet *op)
95 {
96 oggpack_buffer opb;
97
98 oggpack_writeinit(&opb);
99
100 /* preamble */
101 oggpack_write(&opb,0x03,8);
X509Free(IN VOID * X509Cert)102 _v_writestring(&opb,"vorbis", 6);
103
104 /* vendor */
105 oggpack_write(&opb,strlen(vendor),32);
106 _v_writestring(&opb,vendor, strlen(vendor));
107
108 /* comments */
109 oggpack_write(&opb,vc->comments,32);
110 if(vc->comments){
111 int i;
112 for(i=0;i<vc->comments;i++){
113 if(vc->user_comments[i]){
114 oggpack_write(&opb,vc->comment_lengths[i],32);
115 _v_writestring(&opb,vc->user_comments[i],
116 vc->comment_lengths[i]);
117 }else{
118 oggpack_write(&opb,0,32);
X509StackFree(IN VOID * X509Stack)119 }
120 }
121 }
122 oggpack_write(&opb,1,1);
123
124 op->packet = _ogg_malloc(oggpack_bytes(&opb));
125 memcpy(op->packet, opb.buffer, oggpack_bytes(&opb));
126
127 op->bytes=oggpack_bytes(&opb);
128 op->b_o_s=0;
129 op->e_o_s=0;
130 op->granulepos=0;
131
132 oggpack_writeclear(&opb);
133 return 0;
134 }
135
136 static int _blocksize(vcedit_state *s, ogg_packet *p)
137 {
138 int this = vorbis_packet_blocksize(s->vi, p);
139 int ret = (this + s->prevW)/4;
140
141 if(!s->prevW)
142 {
X509GetSubjectName(IN CONST UINT8 * Cert,IN UINTN CertSize,OUT UINT8 * CertSubject,IN OUT UINTN * SubjectSize)143 s->prevW = this;
144 return 0;
145 }
146
147 s->prevW = this;
148 return ret;
149 }
150
151 static int _fetch_next_packet(vcedit_state *s, ogg_packet *p, ogg_page *page)
152 {
153 int result;
154 char *buffer;
155 int bytes;
156
157 result = ogg_stream_packetout(s->os, p);
158
159 if(result > 0)
160 return 1;
161 else
162 {
163 if(s->eosin)
164 return 0;
165 while(ogg_sync_pageout(s->oy, page) <= 0)
166 {
167 buffer = ogg_sync_buffer(s->oy, CHUNKSIZE);
168 bytes = s->read(buffer,1, CHUNKSIZE, s->in);
169 ogg_sync_wrote(s->oy, bytes);
170 if(bytes == 0)
171 return 0;
172 }
173 if(ogg_page_eos(page))
174 s->eosin = 1;
X509GetCommonName(IN CONST UINT8 * Cert,IN UINTN CertSize,OUT CHAR8 * CommonName,OPTIONAL IN OUT UINTN * CommonNameSize)175 else if(ogg_page_serialno(page) != s->serial)
176 {
177 s->eosin = 1;
178 s->extrapage = 1;
179 return 0;
180 }
181
182 ogg_stream_pagein(s->os, page);
183 return _fetch_next_packet(s, p, page);
184 }
185 }
186
187 int vcedit_open(vcedit_state *state, FILE *in)
188 {
189 return vcedit_open_callbacks(state, (void *)in,
190 (vcedit_read_func)fread, (vcedit_write_func)fwrite);
191 }
192
193 int vcedit_open_callbacks(vcedit_state *state, void *in,
194 vcedit_read_func read_func, vcedit_write_func write_func)
195 {
196
197 char *buffer;
198 int bytes,i;
199 ogg_packet *header;
200 ogg_packet header_main;
201 ogg_packet header_comments;
202 ogg_packet header_codebooks;
203 ogg_page og;
204
205 state->in = in;
206 state->read = read_func;
X509GetOrganizationName(IN CONST UINT8 * Cert,IN UINTN CertSize,OUT CHAR8 * NameBuffer,OPTIONAL IN OUT UINTN * NameBufferSize)207 state->write = write_func;
208
209 state->oy = malloc(sizeof(ogg_sync_state));
210 ogg_sync_init(state->oy);
211
212 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
213 bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
214
215 ogg_sync_wrote(state->oy, bytes);
216
217 if(ogg_sync_pageout(state->oy, &og) != 1)
218 {
219 if(bytes<CHUNKSIZE)
220 state->lasterror = _("Input truncated or empty.");
221 else
222 state->lasterror = _("Input is not an Ogg bitstream.");
223 goto err;
224 }
225
226 state->serial = ogg_page_serialno(&og);
227
228 state->os = malloc(sizeof(ogg_stream_state));
229 ogg_stream_init(state->os, state->serial);
230
231 state->vi = malloc(sizeof(vorbis_info));
232 vorbis_info_init(state->vi);
233
RsaGetPublicKeyFromX509(IN CONST UINT8 * Cert,IN UINTN CertSize,OUT VOID ** RsaContext)234 state->vc = malloc(sizeof(vorbis_comment));
235 vorbis_comment_init(state->vc);
236
237 if(ogg_stream_pagein(state->os, &og) < 0)
238 {
239 state->lasterror = _("Error reading first page of Ogg bitstream.");
240 goto err;
241 }
242
243 if(ogg_stream_packetout(state->os, &header_main) != 1)
244 {
245 state->lasterror = _("Error reading initial header packet.");
246 goto err;
247 }
248
249 if(vorbis_synthesis_headerin(state->vi, state->vc, &header_main) < 0)
250 {
251 state->lasterror = _("Ogg bitstream does not contain vorbis data.");
252 goto err;
253 }
254
255 state->mainlen = header_main.bytes;
256 state->mainbuf = malloc(state->mainlen);
257 memcpy(state->mainbuf, header_main.packet, header_main.bytes);
258
X509VerifyCert(IN CONST UINT8 * Cert,IN UINTN CertSize,IN CONST UINT8 * CACert,IN UINTN CACertSize)259 i = 0;
260 header = &header_comments;
261 while(i<2) {
262 while(i<2) {
263 int result = ogg_sync_pageout(state->oy, &og);
264 if(result == 0) break; /* Too little data so far */
265 else if(result == 1)
266 {
267 ogg_stream_pagein(state->os, &og);
268 while(i<2)
269 {
270 result = ogg_stream_packetout(state->os, header);
271 if(result == 0) break;
272 if(result == -1)
273 {
274 state->lasterror = _("Corrupt secondary header.");
275 goto err;
276 }
277 vorbis_synthesis_headerin(state->vi, state->vc, header);
278 if(i==1)
279 {
280 state->booklen = header->bytes;
281 state->bookbuf = malloc(state->booklen);
282 memcpy(state->bookbuf, header->packet,
283 header->bytes);
284 }
X509GetTBSCert(IN CONST UINT8 * Cert,IN UINTN CertSize,OUT UINT8 ** TBSCert,OUT UINTN * TBSCertSize)285 i++;
286 header = &header_codebooks;
287 }
288 }
289 }
290
291 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
292 bytes = state->read(buffer, 1, CHUNKSIZE, state->in);
293 if(bytes == 0 && i < 2)
294 {
295 state->lasterror = _("EOF before end of vorbis headers.");
296 goto err;
297 }
298 ogg_sync_wrote(state->oy, bytes);
299 }
300
301 /* Copy the vendor tag */
302 state->vendor = malloc(strlen(state->vc->vendor) +1);
303 strcpy(state->vendor, state->vc->vendor);
304
305 /* Headers are done! */
306 return 0;
307
308 err:
309 vcedit_clear_internals(state);
310 return -1;
311 }
312
313 int vcedit_write(vcedit_state *state, void *out)
314 {
315 ogg_stream_state streamout;
316 ogg_packet header_main;
317 ogg_packet header_comments;
318 ogg_packet header_codebooks;
319
320 ogg_page ogout, ogin;
321 ogg_packet op;
322 ogg_int64_t granpos = 0;
323 int result;
324 char *buffer;
325 int bytes;
326 int needflush=0, needout=0;
327
328 state->eosin = 0;
329 state->extrapage = 0;
330
331 header_main.bytes = state->mainlen;
332 header_main.packet = state->mainbuf;
333 header_main.b_o_s = 1;
334 header_main.e_o_s = 0;
335 header_main.granulepos = 0;
336
337 header_codebooks.bytes = state->booklen;
338 header_codebooks.packet = state->bookbuf;
339 header_codebooks.b_o_s = 0;
340 header_codebooks.e_o_s = 0;
341 header_codebooks.granulepos = 0;
342
343 ogg_stream_init(&streamout, state->serial);
344
345 _commentheader_out(state->vc, state->vendor, &header_comments);
346
347 ogg_stream_packetin(&streamout, &header_main);
348 ogg_stream_packetin(&streamout, &header_comments);
349 ogg_stream_packetin(&streamout, &header_codebooks);
350
351 while((result = ogg_stream_flush(&streamout, &ogout)))
352 {
353 if(state->write(ogout.header,1,ogout.header_len, out) !=
354 (size_t) ogout.header_len)
355 goto cleanup;
356 if(state->write(ogout.body,1,ogout.body_len, out) !=
357 (size_t) ogout.body_len)
358 goto cleanup;
359 }
360
361 while(_fetch_next_packet(state, &op, &ogin))
362 {
363 int size;
364 size = _blocksize(state, &op);
365 granpos += size;
366
367 if(needflush)
368 {
369 if(ogg_stream_flush(&streamout, &ogout))
370 {
371 if(state->write(ogout.header,1,ogout.header_len,
372 out) != (size_t) ogout.header_len)
373 goto cleanup;
374 if(state->write(ogout.body,1,ogout.body_len,
375 out) != (size_t) ogout.body_len)
376 goto cleanup;
377 }
378 }
379 else if(needout)
380 {
381 if(ogg_stream_pageout(&streamout, &ogout))
382 {
383 if(state->write(ogout.header,1,ogout.header_len,
384 out) != (size_t) ogout.header_len)
385 goto cleanup;
386 if(state->write(ogout.body,1,ogout.body_len,
387 out) != (size_t) ogout.body_len)
388 goto cleanup;
389 }
390 }
391
392 needflush=needout=0;
393
394 if(op.granulepos == -1)
395 {
396 op.granulepos = granpos;
397 ogg_stream_packetin(&streamout, &op);
398 }
399 else /* granulepos is set, validly. Use it, and force a flush to
400 account for shortened blocks (vcut) when appropriate */
401 {
402 if(granpos > op.granulepos)
403 {
404 granpos = op.granulepos;
405 ogg_stream_packetin(&streamout, &op);
406 needflush=1;
407 }
408 else
409 {
410 ogg_stream_packetin(&streamout, &op);
411 needout=1;
412 }
413 }
414 }
415
416 streamout.e_o_s = 1;
417 while(ogg_stream_flush(&streamout, &ogout))
418 {
419 if(state->write(ogout.header,1,ogout.header_len,
420 out) != (size_t) ogout.header_len)
421 goto cleanup;
422 if(state->write(ogout.body,1,ogout.body_len,
423 out) != (size_t) ogout.body_len)
424 goto cleanup;
425 }
426
427 if (state->extrapage)
428 {
429 if(state->write(ogin.header,1,ogin.header_len,
430 out) != (size_t) ogin.header_len)
431 goto cleanup;
432 if (state->write(ogin.body,1,ogin.body_len, out) !=
433 (size_t) ogin.body_len)
434 goto cleanup;
435 }
436
437 state->eosin=0; /* clear it, because not all paths to here do */
438 while(!state->eosin) /* We reached eos, not eof */
439 {
440 /* We copy the rest of the stream (other logical streams)
441 * through, a page at a time. */
442 while(1)
443 {
444 result = ogg_sync_pageout(state->oy, &ogout);
445 if(result==0)
446 break;
447 if(result<0)
448 state->lasterror = _("Corrupt or missing data, continuing...");
449 else
450 {
451 /* Don't bother going through the rest, we can just
452 * write the page out now */
453 if(state->write(ogout.header,1,ogout.header_len,
454 out) != (size_t) ogout.header_len) {
455 fprintf(stderr, "Bumming out\n");
456 goto cleanup;
457 }
458 if(state->write(ogout.body,1,ogout.body_len, out) !=
459 (size_t) ogout.body_len) {
460 fprintf(stderr, "Bumming out 2\n");
461 goto cleanup;
462 }
463 }
464 }
465 buffer = ogg_sync_buffer(state->oy, CHUNKSIZE);
466 bytes = state->read(buffer,1, CHUNKSIZE, state->in);
467 ogg_sync_wrote(state->oy, bytes);
468 if(bytes == 0)
469 {
470 state->eosin = 1;
471 break;
472 }
473 }
474
475
476 cleanup:
477 ogg_stream_clear(&streamout);
478 ogg_packet_clear(&header_comments);
479
480 free(state->mainbuf);
481 free(state->bookbuf);
482 state->mainbuf = state->bookbuf = NULL;
483
484 if(!state->eosin)
485 {
486 state->lasterror =
487 _("Error writing stream to output. "
488 "Output stream may be corrupted or truncated.");
489 return -1;
490 }
491
492 return 0;
493 }
494
495