Thread Sequentieller Parser f. Enctype="multipart/form-data" (1 answers)
Opened by janus at 2015-11-06 11:06

Gast janus
 2015-11-06 11:06
#182786 #182786
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:



more (34.6kb):
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)

View full thread Sequentieller Parser f. Enctype="multipart/form-data"