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