Thread Sequentieller Parser f. Enctype="multipart/form-data"
(1 answers)
Opened by janus at 2015-11-06 11:06
Es gibt gute Gründe für eigene Entwicklungen. Herkömmliche Parser unterstützen lediglich den Content-Type application/x-www-form-urlencoded oder multipart/form-data und unterliegen einer Logik, welche anhand der Request-Methode (POST, GET) über die Herkunft der Daten am Common Gateway entscheidet. So werden die Daten entweder aus STDIN oder aus dem QUERY_STRING gelesen. Es gibt jedoch Anforderungen wie Webservices, RPC, da werden die Daten anders verpackt, zum Beispiel als application/json oder application/soap+xml und ggf. Weitere
Diese Anforderungen begründen eine Eigenentwicklung, welche sich am vom Client gesendeten Content-Type orientiet und nicht an der HTTP-Request-Methode, ja von Letzterer gänzlich unabhängig ist. Bei dieser Gelegenheit habe ich mich nun endgültig von CGI.pm verabschiedet. Habe mir jedoch mal angeschaut, wie L.Stein das so macht. Ich mache es weit weniger kompliziert, untenstehend mein Layer für den Content-Type: multipart/form-data: Code (perl): (dl
)
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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 package ParseMultipart; # Sequentieller Parser f. Enctype="multipart/form-data" use strict; use warnings; use IO::String; use bytes; # Pub. KlassenMethode incl. Konstruktor sub parse_multipart{ my $class = shift; my $handle = shift || *STDIN; binmode $handle; # Lese den Boundary String direkt aus dem Handle my $boundary = <$handle>; # hier wird die Instanz erstellt my $self = bless{ stdin => IO::String->new(), param => {}, boundary => $boundary }, $class; return eval{ $self->_readin($handle); $self->_parse_multipart(); $self->{param}; } } # Private Method, MainLoop sub _parse_multipart{ my $self = shift; my $stdin = $self->{stdin}; $stdin->seek(0,0); # fifo zum Ermitteln der Leerzeile my @fifo = ('','','',''); my $header = ''; # main-loop, gehe in Schritten von 1 byte while( read($stdin, my $byte, 1) ){ shift @fifo; push @fifo, $byte; $header .= $byte; if( join('', @fifo) eq "\r\n\r\n" ){ # hier sind wir auf der ersten Leerzeile $self->_parse_part($header); $header = ''; @fifo = ('','','',''); next; } } } # Parse eine einzelne Komponente sub _parse_part{ my $self = shift; my $head = shift; my $in = $self->{stdin}; my $boundary = $self->{boundary}; # nun lese die binary my $tmp = IO::String->new; # boundary detection my @fifo = split('', $boundary); while( read($in, my $byte, 1) ){ shift @fifo; push @fifo, $byte; $tmp->print($byte); last if join('', @fifo) eq $boundary; } my $content = ''; $tmp->truncate( $tmp->tell() - length($boundary) - 2 ); $tmp->seek(0,0); while(read($tmp, my $buffer, 1024)){ $content .= $buffer; } $tmp->seek(0,0); # parse header Angaben my $content_type = do{ $head =~ /Content-Type: ?(.*)/s; $1 ? unpack "A*", pack "A*", $1 : ''; }; my $name = do{ $head =~ /name="(.*?)"/s; $1 ? $1 : ''; }; my $filename = do{ $head =~ /filename="(.*?)"/s; $1 ? $1 : ''; }; push @{$self->{param}{$name}}, $filename ? { name => $name, content_type => $content_type, filename => $filename, iohandle => $tmp, content_length => length($content), } : $content; } # Lege eine Kopie der Daten aus dem übergebenen Handle an # kopiere in das eigene Handle sub _readin{ my $self = shift; my $handle = shift; while( read($handle, my $buffer, 1024) ){ $self->{stdin}->print($buffer) } } 1;######################################################################### __END__ SYNOPSIS ========== require ParseMultipart; my $param = ParseMultipart->parse_multipart(*STDIN) or die $@; Beispiel: ========= <form method="POST" Enctype="multipart/form-data"> <input type="file" name="file" multiple> <input type="submit" name="load" value="Dateien Hochladen"> <input type="hidden" name='key' value='aged45jhiz8dn62'> </form> Das liefert untenstehende Datenstruktur: ======================================== Hochgeladen wurden 2 Dateien $VAR1 = { 'file' => [ { 'content_length' => 27226, 'content_type' => 'image/png', 'filename' => 'tabbar-solid-bg@2x.png', 'iohandle' => bless( \*Symbol::GEN1, 'IO::String' ), 'name' => 'file' }, { 'content_length' => 7168, 'content_type' => 'application/octet-stream', 'filename' => 'Thumbs.db', 'iohandle' => bless( \*Symbol::GEN2, 'IO::String' ), 'name' => 'file' } ], 'load' => [ 'Dateien Hochladen' ], 'key' => [ 'aged45jhiz8dn62' ] }; Wichtig für Win32 !!!! ======================= binmode STDIN; modedit Editiert von pq: more-tag hinzugefügt Last edited: 2015-12-02 10:10:08 +0100 (CET) |